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