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         mPolicy.mProtocolPoliciesUnsupported = null;
    185         PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, null);
    186         if (!PolicyServiceProxy.isActive(mContext, mPolicy)) {
    187             return false;
    188         }
    189 
    190         // Acknowledge to the server and make sure all's well.
    191         if (performAckRequest(result == RESULT_POLICY_UNSUPPORTED) == RESULT_POLICY_UNSUPPORTED) {
    192             return false;
    193         }
    194 
    195         // Write the final policy key to the Account.
    196         PolicyServiceProxy.setAccountPolicy(mContext, accountId, mPolicy, mPolicyKey);
    197 
    198         // For 12.1 and 14.0, after provisioning we need to also send the device information via
    199         // the Settings command.
    200         // See the comments for EasSettings for more details.
    201         final double version = getProtocolVersion();
    202         if (version == Eas.SUPPORTED_PROTOCOL_EX2007_SP1_DOUBLE
    203                 || version == Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE) {
    204             final EasSettings settingsOperation = new EasSettings(this);
    205             if (!settingsOperation.sendDeviceInformation()) {
    206                 // TODO: Do something more useful when the settings command fails.
    207                 // The consequence here is that the server will not have device info.
    208                 // However, this is NOT a provisioning failure.
    209             }
    210         }
    211 
    212         return true;
    213     }
    214 
    215     @Override
    216     protected String getCommand() {
    217         return "Provision";
    218     }
    219 
    220     /**
    221      * Add the device information to the current request.
    222      * @param context The {@link Context} for the current device.
    223      * @param userAgent The user agent string that our connection uses.
    224      * @param policyKey EAS specific tag for Provision requests.
    225      * @param policyType EAS specific tag for Provision requests.
    226      * @param status The status value that we are sending to the server in our request.
    227      * @param phase The phase of the provisioning process this requests is built for.
    228      * @param protocolVersion The version of the EAS protocol that we should speak.
    229      * @return The {@link Serializer} containing the payload for this request.
    230      */
    231     protected static Serializer generateRequestEntitySerializer(
    232             final Context context, final String userAgent, final String policyKey,
    233             final String policyType, final String status, final int phase,
    234             final double protocolVersion) throws IOException {
    235         final Serializer s = new Serializer();
    236         s.start(Tags.PROVISION_PROVISION);
    237 
    238         // When requesting the policy in 14.1, we also need to send device information.
    239         if (phase == PHASE_INITIAL &&
    240                 protocolVersion >= Eas.SUPPORTED_PROTOCOL_EX2010_SP1_DOUBLE) {
    241             // The "inner" version of this function is being used because it is
    242             // re-entrant and can be unit tested easier.  Until we are unit testing
    243             // everything, the other version of this function still lives so that
    244             // we are disrupting as little code as possible for now.
    245             expandedAddDeviceInformationToSerializer(s, context, userAgent);
    246         }
    247         if (phase == PHASE_WIPE) {
    248             s.start(Tags.PROVISION_REMOTE_WIPE);
    249             s.data(Tags.PROVISION_STATUS, PROVISION_STATUS_OK);
    250             s.end(); // PROVISION_REMOTE_WIPE
    251         } else {
    252             s.start(Tags.PROVISION_POLICIES);
    253             s.start(Tags.PROVISION_POLICY);
    254             s.data(Tags.PROVISION_POLICY_TYPE, policyType);
    255             // When acknowledging a policy, we tell the server whether we applied the policy.
    256             if (phase == PHASE_ACKNOWLEDGE) {
    257                 s.data(Tags.PROVISION_POLICY_KEY, policyKey);
    258                 s.data(Tags.PROVISION_STATUS, status);
    259             }
    260             s.end().end(); // PROVISION_POLICY, PROVISION_POLICIES,
    261         }
    262         s.end().done(); // PROVISION_PROVISION
    263         return s;
    264     }
    265 
    266     /**
    267      * Generates a request entity based on the type of request and our current context.
    268      * @return The {@link HttpEntity} that was generated for this request.
    269      */
    270     @Override
    271     protected HttpEntity getRequestEntity() throws IOException {
    272         final String policyType = getPolicyType();
    273         final String userAgent = getUserAgent();
    274         final double protocolVersion = getProtocolVersion();
    275         final Serializer s = generateRequestEntitySerializer(mContext, userAgent, mPolicyKey,
    276                 policyType, mStatus, mPhase, protocolVersion);
    277         return makeEntity(s);
    278     }
    279 
    280     @Override
    281     protected int handleResponse(final EasResponse response) throws IOException {
    282         final ProvisionParser pp = new ProvisionParser(mContext, response.getInputStream());
    283         // If this is the response for a remote wipe ack, it doesn't have anything useful in it.
    284         // Just go ahead and return now.
    285         if (mPhase == PHASE_WIPE) {
    286             return RESULT_REMOTE_WIPE;
    287         }
    288 
    289         if (!pp.parse()) {
    290             throw new IOException("Error while parsing response");
    291         }
    292 
    293         // What we care about in the response depends on what phase we're in.
    294         if (mPhase == PHASE_INITIAL) {
    295             if (pp.getRemoteWipe()) {
    296                 return RESULT_REMOTE_WIPE;
    297             }
    298             mPolicy = pp.getPolicy();
    299             mPolicyKey = pp.getSecuritySyncKey();
    300 
    301             return (pp.hasSupportablePolicySet()
    302                     ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED);
    303         }
    304 
    305         if (mPhase == PHASE_ACKNOWLEDGE) {
    306             mPolicyKey = pp.getSecuritySyncKey();
    307             return (mPolicyKey != null ? RESULT_POLICY_SUPPORTED : RESULT_POLICY_UNSUPPORTED);
    308         }
    309 
    310         // Note: this should be unreachable, but the compiler doesn't know it.
    311         // If we somehow get here, act like we can't do anything.
    312         return RESULT_POLICY_UNSUPPORTED;
    313     }
    314 
    315     @Override
    316     protected boolean handleProvisionError() {
    317         // If we get a provisioning error while doing provisioning, we should not recurse.
    318         return false;
    319     }
    320 
    321     /**
    322      * @return The policy type for this connection.
    323      */
    324     private final String getPolicyType() {
    325         return (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ?
    326                 EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE;
    327     }
    328 }
    329