|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.db; |
|
6 |
|
7 import java.util.HashMap; |
|
8 |
|
9 import org.mozilla.gecko.GeckoApp; |
|
10 import org.mozilla.gecko.GeckoAppShell; |
|
11 import org.mozilla.gecko.GeckoEvent; |
|
12 import org.mozilla.gecko.NSSBridge; |
|
13 import org.mozilla.gecko.db.BrowserContract.DeletedPasswords; |
|
14 import org.mozilla.gecko.db.BrowserContract.Passwords; |
|
15 import org.mozilla.gecko.mozglue.GeckoLoader; |
|
16 import org.mozilla.gecko.sqlite.MatrixBlobCursor; |
|
17 import org.mozilla.gecko.sqlite.SQLiteBridge; |
|
18 import org.mozilla.gecko.sync.Utils; |
|
19 |
|
20 import android.content.ContentValues; |
|
21 import android.content.Intent; |
|
22 import android.content.UriMatcher; |
|
23 import android.database.Cursor; |
|
24 import android.net.Uri; |
|
25 import android.text.TextUtils; |
|
26 import android.util.Log; |
|
27 |
|
28 public class PasswordsProvider extends SQLiteBridgeContentProvider { |
|
29 static final String TABLE_PASSWORDS = "moz_logins"; |
|
30 static final String TABLE_DELETED_PASSWORDS = "moz_deleted_logins"; |
|
31 |
|
32 private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_PASSWORDS"; |
|
33 |
|
34 private static final int PASSWORDS = 100; |
|
35 private static final int DELETED_PASSWORDS = 101; |
|
36 |
|
37 static final String DEFAULT_PASSWORDS_SORT_ORDER = Passwords.HOSTNAME + " ASC"; |
|
38 static final String DEFAULT_DELETED_PASSWORDS_SORT_ORDER = DeletedPasswords.TIME_DELETED + " ASC"; |
|
39 |
|
40 private static final UriMatcher URI_MATCHER; |
|
41 |
|
42 private static HashMap<String, String> PASSWORDS_PROJECTION_MAP; |
|
43 private static HashMap<String, String> DELETED_PASSWORDS_PROJECTION_MAP; |
|
44 |
|
45 // this should be kept in sync with the version in toolkit/components/passwordmgr/storage-mozStorage.js |
|
46 private static final int DB_VERSION = 5; |
|
47 private static final String DB_FILENAME = "signons.sqlite"; |
|
48 private static final String WHERE_GUID_IS_NULL = BrowserContract.DeletedPasswords.GUID + " IS NULL"; |
|
49 private static final String WHERE_GUID_IS_VALUE = BrowserContract.DeletedPasswords.GUID + " = ?"; |
|
50 |
|
51 private static final String LOG_TAG = "GeckPasswordsProvider"; |
|
52 |
|
53 static { |
|
54 URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); |
|
55 |
|
56 // content://org.mozilla.gecko.providers.browser/passwords/# |
|
57 URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "passwords", PASSWORDS); |
|
58 |
|
59 PASSWORDS_PROJECTION_MAP = new HashMap<String, String>(); |
|
60 PASSWORDS_PROJECTION_MAP.put(Passwords.ID, Passwords.ID); |
|
61 PASSWORDS_PROJECTION_MAP.put(Passwords.HOSTNAME, Passwords.HOSTNAME); |
|
62 PASSWORDS_PROJECTION_MAP.put(Passwords.HTTP_REALM, Passwords.HTTP_REALM); |
|
63 PASSWORDS_PROJECTION_MAP.put(Passwords.FORM_SUBMIT_URL, Passwords.FORM_SUBMIT_URL); |
|
64 PASSWORDS_PROJECTION_MAP.put(Passwords.USERNAME_FIELD, Passwords.USERNAME_FIELD); |
|
65 PASSWORDS_PROJECTION_MAP.put(Passwords.PASSWORD_FIELD, Passwords.PASSWORD_FIELD); |
|
66 PASSWORDS_PROJECTION_MAP.put(Passwords.ENCRYPTED_USERNAME, Passwords.ENCRYPTED_USERNAME); |
|
67 PASSWORDS_PROJECTION_MAP.put(Passwords.ENCRYPTED_PASSWORD, Passwords.ENCRYPTED_PASSWORD); |
|
68 PASSWORDS_PROJECTION_MAP.put(Passwords.GUID, Passwords.GUID); |
|
69 PASSWORDS_PROJECTION_MAP.put(Passwords.ENC_TYPE, Passwords.ENC_TYPE); |
|
70 PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_CREATED, Passwords.TIME_CREATED); |
|
71 PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_LAST_USED, Passwords.TIME_LAST_USED); |
|
72 PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_PASSWORD_CHANGED, Passwords.TIME_PASSWORD_CHANGED); |
|
73 PASSWORDS_PROJECTION_MAP.put(Passwords.TIMES_USED, Passwords.TIMES_USED); |
|
74 |
|
75 URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "deleted-passwords", DELETED_PASSWORDS); |
|
76 |
|
77 DELETED_PASSWORDS_PROJECTION_MAP = new HashMap<String, String>(); |
|
78 DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.ID, DeletedPasswords.ID); |
|
79 DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.GUID, DeletedPasswords.GUID); |
|
80 DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.TIME_DELETED, DeletedPasswords.TIME_DELETED); |
|
81 |
|
82 // We don't use .loadMozGlue because we're in a different process, |
|
83 // and we just want to reuse code rather than use the loader lock etc. |
|
84 GeckoLoader.doLoadLibrary("mozglue"); |
|
85 } |
|
86 |
|
87 public PasswordsProvider() { |
|
88 super(LOG_TAG); |
|
89 } |
|
90 |
|
91 @Override |
|
92 protected String getDBName(){ |
|
93 return DB_FILENAME; |
|
94 } |
|
95 |
|
96 @Override |
|
97 protected String getTelemetryPrefix() { |
|
98 return TELEMETRY_TAG; |
|
99 } |
|
100 |
|
101 @Override |
|
102 protected int getDBVersion(){ |
|
103 return DB_VERSION; |
|
104 } |
|
105 |
|
106 @Override |
|
107 public String getType(Uri uri) { |
|
108 final int match = URI_MATCHER.match(uri); |
|
109 |
|
110 switch (match) { |
|
111 case PASSWORDS: |
|
112 return Passwords.CONTENT_TYPE; |
|
113 |
|
114 case DELETED_PASSWORDS: |
|
115 return DeletedPasswords.CONTENT_TYPE; |
|
116 |
|
117 default: |
|
118 throw new UnsupportedOperationException("Unknown type " + uri); |
|
119 } |
|
120 } |
|
121 |
|
122 @Override |
|
123 public String getTable(Uri uri) { |
|
124 final int match = URI_MATCHER.match(uri); |
|
125 switch (match) { |
|
126 case DELETED_PASSWORDS: |
|
127 return TABLE_DELETED_PASSWORDS; |
|
128 |
|
129 case PASSWORDS: |
|
130 return TABLE_PASSWORDS; |
|
131 |
|
132 default: |
|
133 throw new UnsupportedOperationException("Unknown table " + uri); |
|
134 } |
|
135 } |
|
136 |
|
137 @Override |
|
138 public String getSortOrder(Uri uri, String aRequested) { |
|
139 if (!TextUtils.isEmpty(aRequested)) { |
|
140 return aRequested; |
|
141 } |
|
142 |
|
143 final int match = URI_MATCHER.match(uri); |
|
144 switch (match) { |
|
145 case DELETED_PASSWORDS: |
|
146 return DEFAULT_DELETED_PASSWORDS_SORT_ORDER; |
|
147 |
|
148 case PASSWORDS: |
|
149 return DEFAULT_PASSWORDS_SORT_ORDER; |
|
150 |
|
151 default: |
|
152 throw new UnsupportedOperationException("Unknown URI " + uri); |
|
153 } |
|
154 } |
|
155 |
|
156 @Override |
|
157 public void setupDefaults(Uri uri, ContentValues values) |
|
158 throws IllegalArgumentException { |
|
159 int match = URI_MATCHER.match(uri); |
|
160 long now = System.currentTimeMillis(); |
|
161 switch (match) { |
|
162 case DELETED_PASSWORDS: |
|
163 values.put(DeletedPasswords.TIME_DELETED, now); |
|
164 |
|
165 // Deleted passwords must contain a guid |
|
166 if (!values.containsKey(Passwords.GUID)) { |
|
167 throw new IllegalArgumentException("Must provide a GUID for a deleted password"); |
|
168 } |
|
169 break; |
|
170 |
|
171 case PASSWORDS: |
|
172 values.put(Passwords.TIME_CREATED, now); |
|
173 |
|
174 // Generate GUID for new password. Don't override specified GUIDs. |
|
175 if (!values.containsKey(Passwords.GUID)) { |
|
176 String guid = Utils.generateGuid(); |
|
177 values.put(Passwords.GUID, guid); |
|
178 } |
|
179 String nowString = new Long(now).toString(); |
|
180 DBUtils.replaceKey(values, null, Passwords.HOSTNAME, ""); |
|
181 DBUtils.replaceKey(values, null, Passwords.HTTP_REALM, ""); |
|
182 DBUtils.replaceKey(values, null, Passwords.FORM_SUBMIT_URL, ""); |
|
183 DBUtils.replaceKey(values, null, Passwords.USERNAME_FIELD, ""); |
|
184 DBUtils.replaceKey(values, null, Passwords.PASSWORD_FIELD, ""); |
|
185 DBUtils.replaceKey(values, null, Passwords.ENCRYPTED_USERNAME, ""); |
|
186 DBUtils.replaceKey(values, null, Passwords.ENCRYPTED_PASSWORD, ""); |
|
187 DBUtils.replaceKey(values, null, Passwords.ENC_TYPE, "0"); |
|
188 DBUtils.replaceKey(values, null, Passwords.TIME_LAST_USED, nowString); |
|
189 DBUtils.replaceKey(values, null, Passwords.TIME_PASSWORD_CHANGED, nowString); |
|
190 DBUtils.replaceKey(values, null, Passwords.TIMES_USED, "0"); |
|
191 break; |
|
192 |
|
193 default: |
|
194 throw new UnsupportedOperationException("Unknown URI " + uri); |
|
195 } |
|
196 } |
|
197 |
|
198 @Override |
|
199 public void initGecko() { |
|
200 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Passwords:Init", null)); |
|
201 Intent initIntent = new Intent(GeckoApp.ACTION_INIT_PW); |
|
202 mContext.sendBroadcast(initIntent); |
|
203 } |
|
204 |
|
205 private String doCrypto(String initialValue, Uri uri, Boolean encrypt) { |
|
206 String profilePath = null; |
|
207 if (uri != null) { |
|
208 profilePath = uri.getQueryParameter(BrowserContract.PARAM_PROFILE_PATH); |
|
209 } |
|
210 |
|
211 String result = ""; |
|
212 try { |
|
213 if (encrypt) { |
|
214 if (profilePath != null) { |
|
215 result = NSSBridge.encrypt(mContext, profilePath, initialValue); |
|
216 } else { |
|
217 result = NSSBridge.encrypt(mContext, initialValue); |
|
218 } |
|
219 } else { |
|
220 if (profilePath != null) { |
|
221 result = NSSBridge.decrypt(mContext, profilePath, initialValue); |
|
222 } else { |
|
223 result = NSSBridge.decrypt(mContext, initialValue); |
|
224 } |
|
225 } |
|
226 } catch (Exception ex) { |
|
227 Log.e(LOG_TAG, "Error in NSSBridge"); |
|
228 throw new RuntimeException(ex); |
|
229 } |
|
230 return result; |
|
231 } |
|
232 |
|
233 @Override |
|
234 public void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db) { |
|
235 if (values.containsKey(Passwords.GUID)) { |
|
236 String guid = values.getAsString(Passwords.GUID); |
|
237 if (guid == null) { |
|
238 db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_NULL, null); |
|
239 return; |
|
240 } |
|
241 String[] args = new String[] { guid }; |
|
242 db.delete(TABLE_DELETED_PASSWORDS, WHERE_GUID_IS_VALUE, args); |
|
243 } |
|
244 |
|
245 if (values.containsKey(Passwords.ENCRYPTED_PASSWORD)) { |
|
246 String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_PASSWORD), uri, true); |
|
247 values.put(Passwords.ENCRYPTED_PASSWORD, res); |
|
248 values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR); |
|
249 } |
|
250 |
|
251 if (values.containsKey(Passwords.ENCRYPTED_USERNAME)) { |
|
252 String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_USERNAME), uri, true); |
|
253 values.put(Passwords.ENCRYPTED_USERNAME, res); |
|
254 values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR); |
|
255 } |
|
256 } |
|
257 |
|
258 @Override |
|
259 public void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db) { |
|
260 if (values.containsKey(Passwords.ENCRYPTED_PASSWORD)) { |
|
261 String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_PASSWORD), uri, true); |
|
262 values.put(Passwords.ENCRYPTED_PASSWORD, res); |
|
263 values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR); |
|
264 } |
|
265 |
|
266 if (values.containsKey(Passwords.ENCRYPTED_USERNAME)) { |
|
267 String res = doCrypto(values.getAsString(Passwords.ENCRYPTED_USERNAME), uri, true); |
|
268 values.put(Passwords.ENCRYPTED_USERNAME, res); |
|
269 values.put(Passwords.ENC_TYPE, Passwords.ENCTYPE_SDR); |
|
270 } |
|
271 } |
|
272 |
|
273 @Override |
|
274 public void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db) { |
|
275 int passwordIndex = -1; |
|
276 int usernameIndex = -1; |
|
277 String profilePath = null; |
|
278 |
|
279 try { |
|
280 passwordIndex = cursor.getColumnIndexOrThrow(Passwords.ENCRYPTED_PASSWORD); |
|
281 } catch(Exception ex) { } |
|
282 try { |
|
283 usernameIndex = cursor.getColumnIndexOrThrow(Passwords.ENCRYPTED_USERNAME); |
|
284 } catch(Exception ex) { } |
|
285 |
|
286 if (passwordIndex > -1 || usernameIndex > -1) { |
|
287 MatrixBlobCursor m = (MatrixBlobCursor)cursor; |
|
288 if (cursor.moveToFirst()) { |
|
289 do { |
|
290 if (passwordIndex > -1) { |
|
291 String decrypted = doCrypto(cursor.getString(passwordIndex), uri, false);; |
|
292 m.set(passwordIndex, decrypted); |
|
293 } |
|
294 |
|
295 if (usernameIndex > -1) { |
|
296 String decrypted = doCrypto(cursor.getString(usernameIndex), uri, false); |
|
297 m.set(usernameIndex, decrypted); |
|
298 } |
|
299 } while(cursor.moveToNext()); |
|
300 } |
|
301 } |
|
302 } |
|
303 } |