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