• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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