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