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