SecurityPolicy.java revision 345fb8b737c1632fb2a7e69ac44b8612be6237ed
1/*
2 * Copyright (C) 2010 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.email;
18
19import com.android.email.provider.EmailContent.Account;
20
21import android.app.DeviceAdmin;
22import android.content.Context;
23import android.content.Intent;
24import android.database.Cursor;
25
26/**
27 * Utility functions to support reading and writing security policies
28 */
29public class SecurityPolicy {
30
31    private static SecurityPolicy sInstance = null;
32    private Context mContext;
33
34    /**
35     * This projection on Account is for scanning/reading
36     */
37    private static final String[] ACCOUNT_SECURITY_PROJECTION = new String[] {
38        Account.RECORD_ID, Account.SECURITY_FLAGS
39    };
40    private static final int ACCOUNT_SECURITY_COLUMN_FLAGS = 1;
41    // Note, this handles the NULL case to deal with older accounts where the column was added
42    private static final String WHERE_ACCOUNT_SECURITY_NONZERO =
43        Account.SECURITY_FLAGS + " IS NOT NULL AND " + Account.SECURITY_FLAGS + "!=0";
44
45    /**
46     * Get the security policy instance
47     */
48    public synchronized static SecurityPolicy getInstance(Context context) {
49        if (sInstance == null) {
50            sInstance = new SecurityPolicy(context);
51        }
52        return sInstance;
53    }
54
55    /**
56     * Private constructor (one time only)
57     */
58    private SecurityPolicy(Context context) {
59        mContext = context;
60    }
61
62    /**
63     * For testing only: Inject context into already-created instance
64     */
65    /* package */ void setContext(Context context) {
66        mContext = context;
67    }
68
69    /**
70     * Compute the aggregate policy for all accounts that require it, and record it.
71     *
72     * The business logic is as follows:
73     *  min password length         take the max
74     *  password mode               take the max (strongest mode)
75     *  max password fails          take the min
76     *  max screen lock time        take the min
77     *  require remote wipe         take the max (logical or)
78     *
79     * @return a policy representing the strongest aggregate, or null if none are defined
80     */
81    /* package */ PolicySet computeAggregatePolicy() {
82        boolean policiesFound = false;
83
84        int minPasswordLength = Integer.MIN_VALUE;
85        int passwordMode = Integer.MIN_VALUE;
86        int maxPasswordFails = Integer.MAX_VALUE;
87        int maxScreenLockTime = Integer.MAX_VALUE;
88        boolean requireRemoteWipe = false;
89
90        Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI,
91                ACCOUNT_SECURITY_PROJECTION, WHERE_ACCOUNT_SECURITY_NONZERO, null, null);
92        try {
93            while (c.moveToNext()) {
94                int flags = c.getInt(ACCOUNT_SECURITY_COLUMN_FLAGS);
95                if (flags != 0) {
96                    PolicySet p = new PolicySet(flags);
97                    if (p.mMinPasswordLength > 0) {
98                        minPasswordLength = Math.max(p.mMinPasswordLength, minPasswordLength);
99                    }
100                    if (p.mPasswordMode > 0) {
101                        passwordMode  = Math.max(p.mPasswordMode, passwordMode);
102                    }
103                    if (p.mMaxPasswordFails > 0) {
104                        maxPasswordFails = Math.min(p.mMaxPasswordFails, maxPasswordFails);
105                    }
106                    if (p.mMaxScreenLockTime > 0) {
107                        maxScreenLockTime = Math.min(p.mMaxScreenLockTime, maxScreenLockTime);
108                    }
109                    requireRemoteWipe |= p.mRequireRemoteWipe;
110                    policiesFound = true;
111                }
112            }
113        } finally {
114            c.close();
115        }
116        if (policiesFound) {
117            return new PolicySet(minPasswordLength, passwordMode, maxPasswordFails,
118                    maxScreenLockTime, requireRemoteWipe);
119        } else {
120            return null;
121        }
122    }
123
124    /**
125     * Query used to determine if a given policy is "possible" (irrespective of current
126     * device state.  This is used when creating new accounts.
127     *
128     * TO BE IMPLEMENTED
129     *
130     * @param policies the policies requested
131     * @return true if the policies are supported, false if not supported
132     */
133    public boolean isSupported(PolicySet policies) {
134        return true;
135    }
136
137    /**
138     * Query used to determine if a given policy is "active" (the device is operating at
139     * the required security level).  This is used when creating new accounts.
140     *
141     * TO BE IMPLEMENTED
142     *
143     * @param policies the policies requested
144     * @return true if the policies are active, false if not active
145     */
146    public boolean isActive(PolicySet policies) {
147        return true;
148    }
149
150    /**
151     * Sync service should call this any time a sync fails due to isActive() returning false.
152     * This will kick off the acquire-admin-state process and/or increase the security level.
153     * The caller needs to write the required policies into this account before making this call.
154     *
155     * @param accountId the account for which sync cannot proceed
156     */
157    public void policiesRequired(long accountId) {
158        // implement....
159    }
160
161    /**
162     * Class for tracking policies and reading/writing into accounts
163     */
164    public static class PolicySet {
165
166        // Security (provisioning) flags
167            // bits 0..4: password length (0=no password required)
168        private static final int PASSWORD_LENGTH_MASK = 31;
169        private static final int PASSWORD_LENGTH_SHIFT = 0;
170        public static final int PASSWORD_LENGTH_MAX = 31;
171            // bits 5..8: password mode
172        private static final int PASSWORD_MODE_SHIFT = 5;
173        private static final int PASSWORD_MODE_MASK = 15 << PASSWORD_MODE_SHIFT;
174        public static final int PASSWORD_MODE_NONE = 0 << PASSWORD_MODE_SHIFT;
175        public static final int PASSWORD_MODE_SIMPLE = 1 << PASSWORD_MODE_SHIFT;
176        public static final int PASSWORD_MODE_STRONG = 2 << PASSWORD_MODE_SHIFT;
177            // bits 9..13: password failures -> wipe device (0=disabled)
178        private static final int PASSWORD_MAX_FAILS_SHIFT = 9;
179        private static final int PASSWORD_MAX_FAILS_MASK = 31 << PASSWORD_MAX_FAILS_SHIFT;
180        public static final int PASSWORD_MAX_FAILS_MAX = 31;
181            // bits 14..24: seconds to screen lock (0=not required)
182        private static final int SCREEN_LOCK_TIME_SHIFT = 14;
183        private static final int SCREEN_LOCK_TIME_MASK = 2047 << SCREEN_LOCK_TIME_SHIFT;
184        public static final int SCREEN_LOCK_TIME_MAX = 2047;
185            // bit 25: remote wipe capability required
186        private static final int REQUIRE_REMOTE_WIPE = 1 << 25;
187
188        public final int mMinPasswordLength;
189        public final int mPasswordMode;
190        public final int mMaxPasswordFails;
191        public final int mMaxScreenLockTime;
192        public final boolean mRequireRemoteWipe;
193
194        /**
195         * Create from raw values.
196         * @param minPasswordLength (0=not enforced)
197         * @param passwordMode
198         * @param maxPasswordFails (0=not enforced)
199         * @param maxScreenLockTime (0=not enforced)
200         * @param requireRemoteWipe
201         */
202        public PolicySet(int minPasswordLength, int passwordMode, int maxPasswordFails,
203                int maxScreenLockTime, boolean requireRemoteWipe) {
204            if (minPasswordLength > PASSWORD_LENGTH_MAX) {
205                throw new IllegalArgumentException("password length");
206            }
207            if (passwordMode < PASSWORD_MODE_NONE
208                    || passwordMode > PASSWORD_MODE_STRONG) {
209                throw new IllegalArgumentException("password mode");
210            }
211            if (maxPasswordFails > PASSWORD_MAX_FAILS_MAX) {
212                throw new IllegalArgumentException("password max fails");
213            }
214            if (maxScreenLockTime > SCREEN_LOCK_TIME_MAX) {
215                throw new IllegalArgumentException("max screen lock time");
216            }
217
218            mMinPasswordLength = minPasswordLength;
219            mPasswordMode = passwordMode;
220            mMaxPasswordFails = maxPasswordFails;
221            mMaxScreenLockTime = maxScreenLockTime;
222            mRequireRemoteWipe = requireRemoteWipe;
223        }
224
225        /**
226         * Create from values encoded in an account
227         * @param account
228         */
229        public PolicySet(Account account) {
230            this(account.mSecurityFlags);
231        }
232
233        /**
234         * Create from values encoded in an account flags int
235         */
236        public PolicySet(int flags) {
237            mMinPasswordLength =
238                (flags & PASSWORD_LENGTH_MASK) >> PASSWORD_LENGTH_SHIFT;
239            mPasswordMode =
240                (flags & PASSWORD_MODE_MASK);
241            mMaxPasswordFails =
242                (flags & PASSWORD_MAX_FAILS_MASK) >> PASSWORD_MAX_FAILS_SHIFT;
243            mMaxScreenLockTime =
244                (flags & SCREEN_LOCK_TIME_MASK) >> SCREEN_LOCK_TIME_SHIFT;
245            mRequireRemoteWipe = 0 != (flags & REQUIRE_REMOTE_WIPE);
246        }
247
248        /**
249         * Record flags (and a sync key for the flags) into an Account
250         * Note: the hash code is defined as the encoding used in Account
251         * @param account to write the values mSecurityFlags and mSecuritySyncKey
252         * @param syncKey the value to write into the account's mSecuritySyncKey
253         */
254        public void writeAccount(Account account, String syncKey) {
255          account.mSecurityFlags = hashCode();
256        }
257
258        @Override
259        public boolean equals(Object o) {
260            if (o instanceof PolicySet) {
261                PolicySet other = (PolicySet)o;
262                return (this.mMinPasswordLength == other.mMinPasswordLength)
263                        && (this.mPasswordMode == other.mPasswordMode)
264                        && (this.mMaxPasswordFails == other.mMaxPasswordFails)
265                        && (this.mMaxScreenLockTime == other.mMaxScreenLockTime)
266                        && (this.mRequireRemoteWipe == other.mRequireRemoteWipe);
267            }
268            return false;
269        }
270
271        /**
272         * Note: the hash code is defined as the encoding used in Account
273         */
274        @Override
275        public int hashCode() {
276            int flags = 0;
277            flags = mMinPasswordLength << PASSWORD_LENGTH_SHIFT;
278            flags |= mPasswordMode;
279            flags |= mMaxPasswordFails << PASSWORD_MAX_FAILS_SHIFT;
280            flags |= mMaxScreenLockTime << SCREEN_LOCK_TIME_SHIFT;
281            if (mRequireRemoteWipe) {
282                flags |= REQUIRE_REMOTE_WIPE;
283            }
284            return flags;
285        }
286
287        @Override
288        public String toString() {
289            return "{ " + "pw-len-min=" + mMinPasswordLength + " pw-mode=" + mPasswordMode
290                    + " pw-fails-max=" + mMaxPasswordFails + " screenlock-max="
291                    + mMaxScreenLockTime + " remote-wipe-req=" + mRequireRemoteWipe + "}";
292        }
293    }
294
295    /**
296     * Device Policy administrator.  This is primarily a listener for device state changes.
297     */
298    private static class PolicyAdmin extends DeviceAdmin {
299
300        boolean mEnabled = false;
301
302        /**
303         * Called after the administrator is first enabled.
304         */
305        @Override
306        public void onEnabled(Context context, Intent intent) {
307            mEnabled = true;
308            // do something
309        }
310
311        /**
312         * Called prior to the administrator being disabled.
313         */
314        @Override
315        public void onDisabled(Context context, Intent intent) {
316            mEnabled = false;
317            // do something
318        }
319
320        /**
321         * Called after the user has changed their password.
322         */
323        @Override
324        public void onPasswordChanged(Context context, Intent intent) {
325            // do something
326        }
327
328        /**
329         * Called after the user has failed at entering their current password.
330         */
331        @Override
332        public void onPasswordFailed(Context context, Intent intent) {
333            // do something
334        }
335    }
336}
337