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 17 package com.android.exchange.eas; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 22 import com.android.emailcommon.provider.Account; 23 import com.android.emailcommon.provider.EmailContent; 24 import com.android.emailcommon.provider.Policy; 25 import com.android.emailcommon.service.PolicyServiceProxy; 26 import com.android.exchange.Eas; 27 import com.android.exchange.EasResponse; 28 import com.android.exchange.adapter.ProvisionParser; 29 import com.android.exchange.adapter.Serializer; 30 import com.android.exchange.adapter.Tags; 31 import com.android.exchange.service.EasServerConnection; 32 import com.android.mail.utils.LogUtils; 33 34 import org.apache.http.HttpEntity; 35 36 import java.io.IOException; 37 38 /** 39 * Implements the EAS Provision protocol. 40 * 41 * Provisioning actually consists of two server interactions: 42 * 1) Ask the server for the required policies. 43 * 2) Acknowledge our disposition for enforcing those policies. 44 * 45 * The structure of the requests and response are essentially the same for both, so we use the 46 * same code and vary slightly based on which one we're doing. Also, provisioning responses can tell 47 * us to wipe the device, so we need to handle that too. 48 * TODO: Make it possible to ack separately, possibly by splitting into separate operations. 49 * See http://msdn.microsoft.com/en-us/library/ee203567(v=exchg.80).aspx for more details. 50 */ 51 public class EasProvision extends EasOperation { 52 53 private static final String LOG_TAG = Eas.LOG_TAG; 54 55 /** The policy type for versions of EAS prior to 2007. */ 56 public static final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML"; 57 /** The policy type for versions of EAS starting with 2007. */ 58 public static final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML"; 59 60 /** The EAS protocol Provision status for "we implement all of the policies" */ 61 static final String PROVISION_STATUS_OK = "1"; 62 /** The EAS protocol Provision status meaning "we partially implement the policies" */ 63 static final String PROVISION_STATUS_PARTIAL = "2"; 64 65 /** Value for {@link #mPhase} indicating we're performing the initial request. */ 66 static final int PHASE_INITIAL = 0; 67 /** Value for {@link #mPhase} indicating we're performing the acknowledgement request. */ 68 static final int PHASE_ACKNOWLEDGE = 1; 69 /** Value for {@link #mPhase} indicating we're performing the acknowledgement for a wipe. */ 70 static final int PHASE_WIPE = 2; 71 72 /** 73 * This operation doesn't use public result codes because ultimately the operation answers 74 * a yes/no question. These result codes are used internally only to communicate from 75 * {@link #handleResponse}. 76 */ 77 78 /** Result code indicating the server's policy can be fully supported. */ 79 private static final int RESULT_POLICY_SUPPORTED = 1; 80 /** Result code indicating the server's policy cannot be fully supported. */ 81 private static final int RESULT_POLICY_UNSUPPORTED = 2; 82 /** Result code indicating the server sent a remote wipe directive. */ 83 private static final int RESULT_REMOTE_WIPE = 3; 84 85 private Policy mPolicy; 86 private String mPolicyKey; 87 private String mStatus; 88 89 /** 90 * Because this operation supports variants of the request and parsing, and {@link EasOperation} 91 * has no way to communicate this into {@link #performOperation}, we use this member variable 92 * to vary how {@link #getRequestEntity} and {@link #handleResponse} work. 93 */ 94 private int mPhase; 95 EasProvision(final Context context, final Account account, final EasServerConnection connection)96 public EasProvision(final Context context, final Account account, 97 final EasServerConnection connection) { 98 super(context, account, connection); 99 mPolicy = null; 100 mPolicyKey = null; 101 mStatus = null; 102 mPhase = 0; 103 } 104 EasProvision(final EasOperation parentOperation)105 public EasProvision(final EasOperation parentOperation) { 106 super(parentOperation); 107 mPolicy = null; 108 mPolicyKey = null; 109 mStatus = null; 110 mPhase = 0; 111 } 112 performInitialRequest()113 private int performInitialRequest() { 114 mPhase = PHASE_INITIAL; 115 return performOperation(); 116 } 117 performAckRequestForWipe()118 private void performAckRequestForWipe() { 119 mPhase = PHASE_WIPE; 120 performOperation(); 121 } 122 performAckRequest(final boolean isPartial)123 private int performAckRequest(final boolean isPartial) { 124 mPhase = PHASE_ACKNOWLEDGE; 125 mStatus = isPartial ? PROVISION_STATUS_PARTIAL : PROVISION_STATUS_OK; 126 return performOperation(); 127 } 128 129 /** 130 * Make the provisioning calls to determine if we can handle the required policy. 131 * @return The {@link Policy} if we support it, or null otherwise. 132 */ test()133 public final Policy test() { 134 int result = performInitialRequest(); 135 if (result == RESULT_POLICY_UNSUPPORTED) { 136 // Check if the server will permit partial policies. 137 result = performAckRequest(true); 138 } 139 if (result == RESULT_POLICY_SUPPORTED) { 140 // The server is ok with us not supporting everything, so clear the unsupported ones. 141 mPolicy.mProtocolPoliciesUnsupported = null; 142 } 143 return (result == RESULT_POLICY_SUPPORTED || result == RESULT_POLICY_UNSUPPORTED) 144 ? mPolicy : null; 145 } 146 147 /** 148 * Write the max attachment size that came out of the policy to the Account table in the db. 149 * Once this value is written, the mapping to Account.Settings.MAX_ATTACHMENT_SIZE was 150 * added to point to this column in this table. 151 * @param maxAttachmentSize The max attachment size value that we want to write to the db. 152 */ storeMaxAttachmentSize(final int maxAttachmentSize)153 private void storeMaxAttachmentSize(final int maxAttachmentSize) { 154 final ContentValues values = new ContentValues(1); 155 values.put(EmailContent.AccountColumns.MAX_ATTACHMENT_SIZE, maxAttachmentSize); 156 Account.update(mContext, Account.CONTENT_URI, getAccountId(), values); 157 } 158 159 /** 160 * Get the required policy from the server and enforce it. 161 * @return Whether we succeeded in provisioning this account. 162 */ provision()163 public final boolean provision() { 164 final int result = performInitialRequest(); 165 final long accountId = getAccountId(); 166 167 if (result < 0) { 168 return false; 169 } 170 171 if (result == RESULT_REMOTE_WIPE) { 172 performAckRequestForWipe(); 173 LogUtils.i(LOG_TAG, "Executing remote wipe"); 174 PolicyServiceProxy.remoteWipe(mContext); 175 return false; 176 } 177 178 // Even before the policy is accepted, we can honor this setting since it has nothing 179 // to do with the device policy manager and is requested by the Exchange server. 180 // TODO: This was an error, this is minimum change to disable it. 181 //storeMaxAttachmentSize(mPolicy.mMaxAttachmentSize); 182 183 // Apply the policies (that we support) with the temporary key. 184 if (mPolicy != null) { 185 mPolicy.mProtocolPoliciesUnsupported = null; 186 } 187 PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, null); 188 if (!PolicyServiceProxy.isActive(mContext, mPolicy)) { 189 return false; 190 } 191 192 // Acknowledge to the server and make sure all's well. 193 if (performAckRequest(result == RESULT_POLICY_UNSUPPORTED) == RESULT_POLICY_UNSUPPORTED) { 194 return false; 195 } 196 197 // Write the final policy key to the Account. 198 PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, mPolicyKey); 199 200 // For 12.1 and 14.0, after provisioning we need to also send the device information via 201 // the Settings command. 202 // See the comments for EasSettings for more details. 203 final double version = getProtocolVersion(); 204 if (version == Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE 205 || version == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) { 206 final EasSettings settingsOperation = new EasSettings(this); 207 if (!settingsOperation.sendDeviceInformation()) { 208 // TODO: Do something more useful when the settings command fails. 209 // The consequence here is that the server will not have device info. 210 // However, this is NOT a provisioning failure. 211 } 212 } 213 214 return true; 215 } 216 217 @Override getCommand()218 protected String getCommand() { 219 return "Provision"; 220 } 221 222 /** 223 * Add the device information to the current request. 224 * @param context The {@link Context} for the current device. 225 * @param userAgent The user agent string that our connection uses. 226 * @param policyKey EAS specific tag for Provision requests. 227 * @param policyType EAS specific tag for Provision requests. 228 * @param status The status value that we are sending to the server in our request. 229 * @param phase The phase of the provisioning process this requests is built for. 230 * @param protocolVersion The version of the EAS protocol that we should speak. 231 * @return The {@link Serializer} containing the payload for this request. 232 */ generateRequestEntitySerializer( final Context context, final String userAgent, final String policyKey, final String policyType, final String status, final int phase, final double protocolVersion)233 protected static Serializer generateRequestEntitySerializer( 234 final Context context, final String userAgent, final String policyKey, 235 final String policyType, final String status, final int phase, 236 final double protocolVersion) throws IOException { 237 final Serializer s = new Serializer(); 238 s.start(Tags.PROVISION_PROVISION); 239 240 // When requesting the policy in 14.1, we also need to send device information. 241 if (phase == PHASE_INITIAL && 242 protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) { 243 // The "inner" version of this function is being used because it is 244 // re-entrant and can be unit tested easier. Until we are unit testing 245 // everything, the other version of this function still lives so that 246 // we are disrupting as little code as possible for now. 247 expandedAddDeviceInformationToSerializer(s, context, userAgent); 248 } 249 if (phase == PHASE_WIPE) { 250 s.start(Tags.PROVISION_REMOTE_WIPE); 251 s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK); 252 s.end(); // PROVISION_REMOTE_WIPE 253 } else { 254 s.start(Tags.PROVISION_POLICIES); 255 s.start(Tags.PROVISION_POLICY); 256 s.data(Tags.PROVISION_POLICY_TYPE, policyType); 257 // When acknowledging a policy, we tell the server whether we applied the policy. 258 if (phase == PHASE_ACKNOWLEDGE) { 259 s.data(Tags.PROVISION_POLICY_KEY, policyKey); 260 s.data(Tags.PROVISION_STATUS, status); 261 } 262 s.end().end(); // PROVISION_POLICY, PROVISION_POLICIES, 263 } 264 s.end().done(); // PROVISION_PROVISION 265 return s; 266 } 267 268 /** 269 * Generates a request entity based on the type of request and our current context. 270 * @return The {@link HttpEntity} that was generated for this request. 271 */ 272 @Override getRequestEntity()273 protected HttpEntity getRequestEntity() throws IOException { 274 final String policyType = getPolicyType(); 275 final String userAgent = getUserAgent(); 276 final double protocolVersion = getProtocolVersion(); 277 final Serializer s = generateRequestEntitySerializer(mContext, userAgent, mPolicyKey, 278 policyType, mStatus, mPhase, protocolVersion); 279 return makeEntity(s); 280 } 281 282 @Override handleResponse(final EasResponse response)283 protected int handleResponse(final EasResponse response) throws IOException { 284 final ProvisionParser pp = new ProvisionParser(mContext, response.getInputStream()); 285 // If this is the response for a remote wipe ack, it doesn't have anything useful in it. 286 // Just go ahead and return now. 287 if (mPhase == PHASE_WIPE) { 288 return RESULT_REMOTE_WIPE; 289 } 290 291 if (!pp.parse()) { 292 throw new IOException("Error while parsing response"); 293 } 294 295 // What we care about in the response depends on what phase we're in. 296 if (mPhase == PHASE_INITIAL) { 297 if (pp.getRemoteWipe()) { 298 return RESULT_REMOTE_WIPE; 299 } 300 mPolicy = pp.getPolicy(); 301 mPolicyKey = pp.getSecuritySyncKey(); 302 303 return (pp.hasSupportablePolicySet() 304 ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED); 305 } 306 307 if (mPhase == PHASE_ACKNOWLEDGE) { 308 mPolicyKey = pp.getSecuritySyncKey(); 309 return (mPolicyKey != null ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED); 310 } 311 312 // Note: this should be unreachable, but the compiler doesn't know it. 313 // If we somehow get here, act like we can't do anything. 314 return RESULT_POLICY_UNSUPPORTED; 315 } 316 317 @Override handleProvisionError()318 protected boolean handleProvisionError() { 319 // If we get a provisioning error while doing provisioning, we should not recurse. 320 return false; 321 } 322 323 /** 324 * @return The policy type for this connection. 325 */ getPolicyType()326 private final String getPolicyType() { 327 return (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? 328 EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE; 329 } 330 } 331