1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.exchange.eas; 18 19import android.content.Context; 20import android.content.SyncResult; 21 22import com.android.emailcommon.provider.Policy; 23import com.android.emailcommon.service.PolicyServiceProxy; 24import com.android.exchange.Eas; 25import com.android.exchange.EasResponse; 26import com.android.exchange.adapter.ProvisionParser; 27import com.android.exchange.adapter.Serializer; 28import com.android.exchange.adapter.Tags; 29import com.android.exchange.service.EasServerConnection; 30import com.android.mail.utils.LogUtils; 31 32import org.apache.http.HttpEntity; 33 34import java.io.IOException; 35 36/** 37 * Implements the EAS Provision protocol. 38 * 39 * Provisioning actually consists of two server interactions: 40 * 1) Ask the server for the required policies. 41 * 2) Acknowledge our disposition for enforcing those policies. 42 * 43 * The structure of the requests and response are essentially the same for both, so we use the 44 * same code and vary slightly based on which one we're doing. Also, provisioning responses can tell 45 * us to wipe the device, so we need to handle that too. 46 * TODO: Make it possible to ack separately, possibly by splitting into separate operations. 47 * See http://msdn.microsoft.com/en-us/library/ee203567(v=exchg.80).aspx for more details. 48 */ 49public class EasProvision extends EasOperation { 50 51 private static final String LOG_TAG = Eas.LOG_TAG; 52 53 /** The policy type for versions of EAS prior to 2007. */ 54 public static final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML"; 55 /** The policy type for versions of EAS starting with 2007. */ 56 public static final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML"; 57 58 /** The EAS protocol Provision status for "we implement all of the policies" */ 59 private static final String PROVISION_STATUS_OK = "1"; 60 /** The EAS protocol Provision status meaning "we partially implement the policies" */ 61 private static final String PROVISION_STATUS_PARTIAL = "2"; 62 63 /** Value for {@link #mPhase} indicating we're performing the initial request. */ 64 private static final int PHASE_INITIAL = 0; 65 /** Value for {@link #mPhase} indicating we're performing the acknowledgement request. */ 66 private static final int PHASE_ACKNOWLEDGE = 1; 67 /** Value for {@link #mPhase} indicating we're performing the acknowledgement for a wipe. */ 68 private static final int PHASE_WIPE = 2; 69 70 /** 71 * This operation doesn't use public result codes because ultimately the operation answers 72 * a yes/no question. These result codes are used internally only to communicate from 73 * {@link #handleResponse}. 74 */ 75 76 /** Result code indicating the server's policy can be fully supported. */ 77 private static final int RESULT_POLICY_SUPPORTED = 1; 78 /** Result code indicating the server's policy cannot be fully supported. */ 79 private static final int RESULT_POLICY_UNSUPPORTED = 2; 80 /** Result code indicating the server sent a remote wipe directive. */ 81 private static final int RESULT_REMOTE_WIPE = 3; 82 83 private Policy mPolicy; 84 private String mPolicyKey; 85 private String mStatus; 86 87 /** 88 * Because this operation supports variants of the request and parsing, and {@link EasOperation} 89 * has no way to communicate this into {@link #performOperation}, we use this member variable 90 * to vary how {@link #getRequestEntity} and {@link #handleResponse} work. 91 */ 92 private int mPhase; 93 94 // TODO: Temporary until EasSyncHandler converts to EasOperation. 95 public EasProvision(final Context context, final long accountId, 96 final EasServerConnection connection) { 97 super(context, accountId, connection); 98 mPolicy = null; 99 mPolicyKey = null; 100 mStatus = null; 101 mPhase = 0; 102 } 103 104 public EasProvision(final EasOperation parentOperation) { 105 super(parentOperation); 106 mPolicy = null; 107 mPolicyKey = null; 108 mStatus = null; 109 mPhase = 0; 110 } 111 112 private int performInitialRequest(final SyncResult syncResult) { 113 mPhase = PHASE_INITIAL; 114 return performOperation(syncResult); 115 } 116 117 private void performAckRequestForWipe(final SyncResult syncResult) { 118 mPhase = PHASE_WIPE; 119 performOperation(syncResult); 120 } 121 122 private int performAckRequest(final SyncResult syncResult, final boolean isPartial) { 123 mPhase = PHASE_ACKNOWLEDGE; 124 mStatus = isPartial ? PROVISION_STATUS_PARTIAL : PROVISION_STATUS_OK; 125 return performOperation(syncResult); 126 } 127 128 /** 129 * Make the provisioning calls to determine if we can handle the required policy. 130 * @return The {@link Policy} if we support it, or null otherwise. 131 */ 132 public final Policy test() { 133 int result = performInitialRequest(null); 134 if (result == RESULT_POLICY_UNSUPPORTED) { 135 // Check if the server will permit partial policies. 136 result = performAckRequest(null, true); 137 } 138 if (result == RESULT_POLICY_SUPPORTED) { 139 // The server is ok with us not supporting everything, so clear the unsupported ones. 140 mPolicy.mProtocolPoliciesUnsupported = null; 141 } 142 return (result == RESULT_POLICY_SUPPORTED || result == RESULT_POLICY_UNSUPPORTED) 143 ? mPolicy : null; 144 } 145 146 /** 147 * Get the required policy from the server and enforce it. 148 * @param syncResult The {@link SyncResult}, if anym for this operation. 149 * @param accountId The id for the account for this request. 150 * @return Whether we succeeded in provisioning this account. 151 */ 152 public final boolean provision(final SyncResult syncResult, final long accountId) { 153 final int result = performInitialRequest(syncResult); 154 155 if (result < 0) { 156 return false; 157 } 158 159 if (result == RESULT_REMOTE_WIPE) { 160 performAckRequestForWipe(syncResult); 161 LogUtils.i(LOG_TAG, "Executing remote wipe"); 162 PolicyServiceProxy.remoteWipe(mContext); 163 return false; 164 } 165 166 // Apply the policies (that we support) with the temporary key. 167 mPolicy.mProtocolPoliciesUnsupported = null; 168 PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, null); 169 if (!PolicyServiceProxy.isActive(mContext, mPolicy)) { 170 return false; 171 } 172 173 // Acknowledge to the server and make sure all's well. 174 if (performAckRequest(syncResult, result == RESULT_POLICY_UNSUPPORTED) == 175 RESULT_POLICY_UNSUPPORTED) { 176 return false; 177 } 178 179 // Write the final policy key to the Account. 180 PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, mPolicyKey); 181 182 // For 12.1 and 14.0, after provisioning we need to also send the device information via 183 // the Settings command. 184 // See the comments for EasSettings for more details. 185 final double version = getProtocolVersion(); 186 if (version == Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE 187 || version == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) { 188 final EasSettings settingsOperation = new EasSettings(this); 189 if (!settingsOperation.sendDeviceInformation(syncResult)) { 190 // TODO: Do something more useful when the settings command fails. 191 // The consequence here is that the server will not have device info. 192 // However, this is NOT a provisioning failure. 193 } 194 } 195 196 return true; 197 } 198 199 @Override 200 protected String getCommand() { 201 return "Provision"; 202 } 203 204 @Override 205 protected HttpEntity getRequestEntity() throws IOException { 206 final Serializer s = new Serializer(); 207 s.start(Tags.PROVISION_PROVISION); 208 209 // When requesting the policy in 14.1, we also need to send device information. 210 if (mPhase == PHASE_INITIAL && 211 getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) { 212 addDeviceInformationToSerlializer(s); 213 } 214 s.start(Tags.PROVISION_POLICIES); 215 s.start(Tags.PROVISION_POLICY); 216 s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType()); 217 218 // When acknowledging a policy, we tell the server whether we applied the policy. 219 if (mPhase == PHASE_ACKNOWLEDGE) { 220 s.data(Tags.PROVISION_POLICY_KEY, mPolicyKey); 221 s.data(Tags.PROVISION_STATUS, mStatus); 222 } 223 if (mPhase == PHASE_WIPE) { 224 s.start(Tags.PROVISION_REMOTE_WIPE); 225 s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK); 226 s.end(); 227 } 228 s.end().end().end().done(); // PROVISION_POLICY, PROVISION_POLICIES, PROVISION_PROVISION 229 230 return makeEntity(s); 231 } 232 233 @Override 234 protected int handleResponse(final EasResponse response, final SyncResult syncResult) 235 throws IOException { 236 final ProvisionParser pp = new ProvisionParser(mContext, response.getInputStream()); 237 // If this is the response for a remote wipe ack, it doesn't have anything useful in it. 238 // Just go ahead and return now. 239 if (mPhase == PHASE_WIPE) { 240 return RESULT_REMOTE_WIPE; 241 } 242 243 if (!pp.parse()) { 244 throw new IOException("Error while parsing response"); 245 } 246 247 // What we care about in the response depends on what phase we're in. 248 if (mPhase == PHASE_INITIAL) { 249 if (pp.getRemoteWipe()) { 250 return RESULT_REMOTE_WIPE; 251 } 252 mPolicy = pp.getPolicy(); 253 mPolicyKey = pp.getSecuritySyncKey(); 254 255 return (pp.hasSupportablePolicySet() 256 ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED); 257 } 258 259 if (mPhase == PHASE_ACKNOWLEDGE) { 260 mPolicyKey = pp.getSecuritySyncKey(); 261 return (mPolicyKey != null ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED); 262 } 263 264 // Note: this should be unreachable, but the compiler doesn't know it. 265 // If we somehow get here, act like we can't do anything. 266 return RESULT_POLICY_UNSUPPORTED; 267 } 268 269 @Override 270 protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) { 271 // If we get a provisioning error while doing provisioning, we should not recurse. 272 return false; 273 } 274 275 /** 276 * @return The policy type for this connection. 277 */ 278 private final String getPolicyType() { 279 return (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? 280 EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE; 281 } 282} 283