Home | History | Annotate | Download | only in eas
      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 
     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 
    105     public EasProvision(final EasOperation parentOperation) {
    106         super(parentOperation);
    107         mPolicy = null;
    108         mPolicyKey = null;
    109         mStatus = null;
    110         mPhase = 0;
    111     }
    112 
    113     private int performInitialRequest() {
    114         mPhase = PHASE_INITIAL;
    115         return performOperation();
    116     }
    117 
    118     private void performAckRequestForWipe() {
    119         mPhase = PHASE_WIPE;
    120         performOperation();
    121     }
    122 
    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      */
    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      */
    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      */
    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
    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      */
    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
    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
    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
    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      */
    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