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 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 105 public EasProvision(final EasOperation parentOperation) { 106 super(parentOperation); 107 mPolicy = null; 108 mPolicyKey = null; 109 mStatus = null; 110 mPhase = 0; 111 } 112 113 private int performInitialRequest() { 114 mPhase = PHASE_INITIAL; 115 return performOperation(); 116 } 117 118 private void performAckRequestForWipe() { 119 mPhase = PHASE_WIPE; 120 performOperation(); 121 } 122 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 */ 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 */ 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 */ 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 mPolicy.mProtocolPoliciesUnsupported = null; 185 PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, null); 186 if (!PolicyServiceProxy.isActive(mContext, mPolicy)) { 187 return false; 188 } 189 190 // Acknowledge to the server and make sure all's well. 191 if (performAckRequest(result == RESULT_POLICY_UNSUPPORTED) == RESULT_POLICY_UNSUPPORTED) { 192 return false; 193 } 194 195 // Write the final policy key to the Account. 196 PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, mPolicyKey); 197 198 // For 12.1 and 14.0, after provisioning we need to also send the device information via 199 // the Settings command. 200 // See the comments for EasSettings for more details. 201 final double version = getProtocolVersion(); 202 if (version == Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE 203 || version == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) { 204 final EasSettings settingsOperation = new EasSettings(this); 205 if (!settingsOperation.sendDeviceInformation()) { 206 // TODO: Do something more useful when the settings command fails. 207 // The consequence here is that the server will not have device info. 208 // However, this is NOT a provisioning failure. 209 } 210 } 211 212 return true; 213 } 214 215 @Override 216 protected String getCommand() { 217 return "Provision"; 218 } 219 220 /** 221 * Add the device information to the current request. 222 * @param context The {@link Context} for the current device. 223 * @param userAgent The user agent string that our connection uses. 224 * @param policyKey EAS specific tag for Provision requests. 225 * @param policyType EAS specific tag for Provision requests. 226 * @param status The status value that we are sending to the server in our request. 227 * @param phase The phase of the provisioning process this requests is built for. 228 * @param protocolVersion The version of the EAS protocol that we should speak. 229 * @return The {@link Serializer} containing the payload for this request. 230 */ 231 protected static Serializer generateRequestEntitySerializer( 232 final Context context, final String userAgent, final String policyKey, 233 final String policyType, final String status, final int phase, 234 final double protocolVersion) throws IOException { 235 final Serializer s = new Serializer(); 236 s.start(Tags.PROVISION_PROVISION); 237 238 // When requesting the policy in 14.1, we also need to send device information. 239 if (phase == PHASE_INITIAL && 240 protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) { 241 // The "inner" version of this function is being used because it is 242 // re-entrant and can be unit tested easier. Until we are unit testing 243 // everything, the other version of this function still lives so that 244 // we are disrupting as little code as possible for now. 245 expandedAddDeviceInformationToSerializer(s, context, userAgent); 246 } 247 if (phase == PHASE_WIPE) { 248 s.start(Tags.PROVISION_REMOTE_WIPE); 249 s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK); 250 s.end(); // PROVISION_REMOTE_WIPE 251 } else { 252 s.start(Tags.PROVISION_POLICIES); 253 s.start(Tags.PROVISION_POLICY); 254 s.data(Tags.PROVISION_POLICY_TYPE, policyType); 255 // When acknowledging a policy, we tell the server whether we applied the policy. 256 if (phase == PHASE_ACKNOWLEDGE) { 257 s.data(Tags.PROVISION_POLICY_KEY, policyKey); 258 s.data(Tags.PROVISION_STATUS, status); 259 } 260 s.end().end(); // PROVISION_POLICY, PROVISION_POLICIES, 261 } 262 s.end().done(); // PROVISION_PROVISION 263 return s; 264 } 265 266 /** 267 * Generates a request entity based on the type of request and our current context. 268 * @return The {@link HttpEntity} that was generated for this request. 269 */ 270 @Override 271 protected HttpEntity getRequestEntity() throws IOException { 272 final String policyType = getPolicyType(); 273 final String userAgent = getUserAgent(); 274 final double protocolVersion = getProtocolVersion(); 275 final Serializer s = generateRequestEntitySerializer(mContext, userAgent, mPolicyKey, 276 policyType, mStatus, mPhase, protocolVersion); 277 return makeEntity(s); 278 } 279 280 @Override 281 protected int handleResponse(final EasResponse response) throws IOException { 282 final ProvisionParser pp = new ProvisionParser(mContext, response.getInputStream()); 283 // If this is the response for a remote wipe ack, it doesn't have anything useful in it. 284 // Just go ahead and return now. 285 if (mPhase == PHASE_WIPE) { 286 return RESULT_REMOTE_WIPE; 287 } 288 289 if (!pp.parse()) { 290 throw new IOException("Error while parsing response"); 291 } 292 293 // What we care about in the response depends on what phase we're in. 294 if (mPhase == PHASE_INITIAL) { 295 if (pp.getRemoteWipe()) { 296 return RESULT_REMOTE_WIPE; 297 } 298 mPolicy = pp.getPolicy(); 299 mPolicyKey = pp.getSecuritySyncKey(); 300 301 return (pp.hasSupportablePolicySet() 302 ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED); 303 } 304 305 if (mPhase == PHASE_ACKNOWLEDGE) { 306 mPolicyKey = pp.getSecuritySyncKey(); 307 return (mPolicyKey != null ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED); 308 } 309 310 // Note: this should be unreachable, but the compiler doesn't know it. 311 // If we somehow get here, act like we can't do anything. 312 return RESULT_POLICY_UNSUPPORTED; 313 } 314 315 @Override 316 protected boolean handleProvisionError() { 317 // If we get a provisioning error while doing provisioning, we should not recurse. 318 return false; 319 } 320 321 /** 322 * @return The policy type for this connection. 323 */ 324 private final String getPolicyType() { 325 return (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? 326 EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE; 327 } 328 } 329