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