| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.exchange.eas; |
| |
| import android.content.SyncResult; |
| |
| import com.android.emailcommon.provider.Policy; |
| import com.android.emailcommon.service.PolicyServiceProxy; |
| import com.android.exchange.Eas; |
| import com.android.exchange.EasResponse; |
| import com.android.exchange.adapter.ProvisionParser; |
| import com.android.exchange.adapter.Serializer; |
| import com.android.exchange.adapter.Tags; |
| import com.android.mail.utils.LogUtils; |
| |
| import org.apache.http.HttpEntity; |
| |
| import java.io.IOException; |
| |
| /** |
| * Implements the EAS Provision protocol. |
| * |
| * Provisioning actually consists of two server interactions: |
| * 1) Ask the server for the required policies. |
| * 2) Acknowledge our disposition for enforcing those policies. |
| * |
| * The structure of the requests and response are essentially the same for both, so we use the |
| * same code and vary slightly based on which one we're doing. Also, provisioning responses can tell |
| * us to wipe the device, so we need to handle that too. |
| * TODO: Make it possible to ack separately, possibly by splitting into separate operations. |
| * See http://msdn.microsoft.com/en-us/library/ee203567(v=exchg.80).aspx for more details. |
| */ |
| public class EasProvision extends EasOperation { |
| |
| private static final String LOG_TAG = "EasProvision"; |
| |
| /** The policy type for versions of EAS prior to 2007. */ |
| public static final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML"; |
| /** The policy type for versions of EAS starting with 2007. */ |
| public static final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML"; |
| |
| /** The EAS protocol Provision status for "we implement all of the policies" */ |
| private static final String PROVISION_STATUS_OK = "1"; |
| /** The EAS protocol Provision status meaning "we partially implement the policies" */ |
| private static final String PROVISION_STATUS_PARTIAL = "2"; |
| |
| /** Value for {@link #mPhase} indicating we're performing the initial request. */ |
| private static final int PHASE_INITIAL = 0; |
| /** Value for {@link #mPhase} indicating we're performing the acknowledgement request. */ |
| private static final int PHASE_ACKNOWLEDGE = 1; |
| /** Value for {@link #mPhase} indicating we're performing the acknowledgement for a wipe. */ |
| private static final int PHASE_WIPE = 2; |
| |
| /** |
| * This operation doesn't use public result codes because ultimately the operation answers |
| * a yes/no question. These result codes are used internally only to communicate from |
| * {@link #handleResponse}. |
| */ |
| |
| /** Result code indicating the server's policy can be fully supported. */ |
| private static final int RESULT_POLICY_SUPPORTED = 1; |
| /** Result code indicating the server's policy cannot be fully supported. */ |
| private static final int RESULT_POLICY_UNSUPPORTED = 2; |
| /** Result code indicating the server sent a remote wipe directive. */ |
| private static final int RESULT_REMOTE_WIPE = 3; |
| |
| private Policy mPolicy; |
| private String mPolicyKey; |
| private String mStatus; |
| |
| /** |
| * Because this operation supports variants of the request and parsing, and {@link EasOperation} |
| * has no way to communicate this into {@link #performOperation}, we use this member variable |
| * to vary how {@link #getRequestEntity} and {@link #handleResponse} work. |
| */ |
| private int mPhase; |
| |
| public EasProvision(final EasOperation parentOperation) { |
| super(parentOperation); |
| mPolicy = null; |
| mPolicyKey = null; |
| mStatus = null; |
| mPhase = 0; |
| } |
| |
| private int performInitialRequest(final SyncResult syncResult) { |
| mPhase = PHASE_INITIAL; |
| return performOperation(syncResult); |
| } |
| |
| private void performAckRequestForWipe(final SyncResult syncResult) { |
| mPhase = PHASE_WIPE; |
| performOperation(syncResult); |
| } |
| |
| private int performAckRequest(final SyncResult syncResult, final boolean isPartial) { |
| mPhase = PHASE_ACKNOWLEDGE; |
| mStatus = isPartial ? PROVISION_STATUS_PARTIAL : PROVISION_STATUS_OK; |
| return performOperation(syncResult); |
| } |
| |
| /** |
| * Make the provisioning calls to determine if we can handle the required policy. |
| * @return The {@link Policy} if we support it, or null otherwise. |
| */ |
| public final Policy test() { |
| int result = performInitialRequest(null); |
| if (result == RESULT_POLICY_UNSUPPORTED) { |
| // Check if the server will permit partial policies. |
| result = performAckRequest(null, true); |
| } |
| if (result == RESULT_POLICY_SUPPORTED) { |
| // The server is ok with us not supporting everything, so clear the unsupported ones. |
| mPolicy.mProtocolPoliciesUnsupported = null; |
| } |
| return (result == RESULT_POLICY_SUPPORTED || result == RESULT_POLICY_UNSUPPORTED) |
| ? mPolicy : null; |
| } |
| |
| /** |
| * Get the required policy from the server and enforce it. |
| * @param syncResult The {@link SyncResult}, if anym for this operation. |
| * @param accountId The id for the account for this request. |
| * @return Whether we succeeded in provisioning this account. |
| */ |
| public final boolean provision(final SyncResult syncResult, final long accountId) { |
| final int result = performInitialRequest(syncResult); |
| |
| if (result < 0) { |
| return false; |
| } |
| |
| if (result == RESULT_REMOTE_WIPE) { |
| performAckRequestForWipe(syncResult); |
| LogUtils.i(LOG_TAG, "Executing remote wipe"); |
| PolicyServiceProxy.remoteWipe(mContext); |
| return false; |
| } |
| |
| // Apply the policies (that we support) with the temporary key. |
| mPolicy.mProtocolPoliciesUnsupported = null; |
| PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, null); |
| if (!PolicyServiceProxy.isActive(mContext, mPolicy)) { |
| return false; |
| } |
| |
| // Acknowledge to the server and make sure all's well. |
| if (performAckRequest(syncResult, result == RESULT_POLICY_UNSUPPORTED) == |
| RESULT_POLICY_UNSUPPORTED) { |
| return false; |
| } |
| |
| // Write the final policy key to the Account. |
| PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, mPolicyKey); |
| |
| // For 12.1 and 14.0, after provisioning we need to also send the device information via |
| // the Settings command. |
| // See the comments for EasSettings for more details. |
| final double version = getProtocolVersion(); |
| if (version == Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE |
| || version == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) { |
| final EasSettings settingsOperation = new EasSettings(this); |
| if (!settingsOperation.sendDeviceInformation(syncResult)) { |
| // If the Settings command failed, so do we. |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected String getCommand() { |
| return "Provision"; |
| } |
| |
| @Override |
| protected HttpEntity getRequestEntity() throws IOException { |
| final Serializer s = new Serializer(); |
| s.start(Tags.PROVISION_PROVISION); |
| |
| // When requesting the policy in 14.1, we also need to send device information. |
| if (mPhase == PHASE_INITIAL && |
| getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) { |
| addDeviceInformationToSerlializer(s); |
| } |
| s.start(Tags.PROVISION_POLICIES); |
| s.start(Tags.PROVISION_POLICY); |
| s.data(Tags.PROVISION_POLICY_TYPE, getPolicyType()); |
| |
| // When acknowledging a policy, we tell the server whether we applied the policy. |
| if (mPhase == PHASE_ACKNOWLEDGE) { |
| s.data(Tags.PROVISION_POLICY_KEY, mPolicyKey); |
| s.data(Tags.PROVISION_STATUS, mStatus); |
| } |
| if (mPhase == PHASE_WIPE) { |
| s.start(Tags.PROVISION_REMOTE_WIPE); |
| s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK); |
| s.end(); |
| } |
| s.end().end().end().done(); // PROVISION_POLICY, PROVISION_POLICIES, PROVISION_PROVISION |
| |
| return makeEntity(s); |
| } |
| |
| @Override |
| protected int handleResponse(final EasResponse response, final SyncResult syncResult) |
| throws IOException { |
| final ProvisionParser pp = new ProvisionParser(mContext, response.getInputStream()); |
| // If this is the response for a remote wipe ack, it doesn't have anything useful in it. |
| // Just go ahead and return now. |
| if (mPhase == PHASE_WIPE) { |
| return RESULT_REMOTE_WIPE; |
| } |
| |
| if (!pp.parse()) { |
| throw new IOException("Error while parsing response"); |
| } |
| |
| // What we care about in the response depends on what phase we're in. |
| if (mPhase == PHASE_INITIAL) { |
| if (pp.getRemoteWipe()) { |
| return RESULT_REMOTE_WIPE; |
| } |
| mPolicy = pp.getPolicy(); |
| mPolicyKey = pp.getSecuritySyncKey(); |
| |
| return (pp.hasSupportablePolicySet() |
| ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED); |
| } |
| |
| if (mPhase == PHASE_ACKNOWLEDGE) { |
| mPolicyKey = pp.getSecuritySyncKey(); |
| return (mPolicyKey != null ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED); |
| } |
| |
| // Note: this should be unreachable, but the compiler doesn't know it. |
| // If we somehow get here, act like we can't do anything. |
| return RESULT_POLICY_UNSUPPORTED; |
| } |
| |
| @Override |
| protected boolean handleProvisionError(final SyncResult syncResult, final long accountId) { |
| // If we get a provisioning error while doing provisioning, we should not recurse. |
| return false; |
| } |
| |
| /** |
| * @return The policy type for this connection. |
| */ |
| private final String getPolicyType() { |
| return (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? |
| EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE; |
| } |
| } |