|
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 |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 package org.mozilla.gecko.sync.setup; |
|
6 |
|
7 import java.io.UnsupportedEncodingException; |
|
8 import java.security.NoSuchAlgorithmException; |
|
9 |
|
10 import org.mozilla.gecko.background.common.log.Logger; |
|
11 import org.mozilla.gecko.sync.SyncConstants; |
|
12 import org.mozilla.gecko.sync.Utils; |
|
13 import org.mozilla.gecko.sync.setup.activities.SetupSyncActivity; |
|
14 |
|
15 import android.accounts.AbstractAccountAuthenticator; |
|
16 import android.accounts.Account; |
|
17 import android.accounts.AccountAuthenticatorResponse; |
|
18 import android.accounts.AccountManager; |
|
19 import android.accounts.NetworkErrorException; |
|
20 import android.app.Service; |
|
21 import android.content.Context; |
|
22 import android.content.Intent; |
|
23 import android.os.Bundle; |
|
24 import android.os.IBinder; |
|
25 |
|
26 public class SyncAuthenticatorService extends Service { |
|
27 private static final String LOG_TAG = "SyncAuthService"; |
|
28 |
|
29 private SyncAccountAuthenticator sAccountAuthenticator = null; |
|
30 |
|
31 @Override |
|
32 public void onCreate() { |
|
33 Logger.debug(LOG_TAG, "onCreate"); |
|
34 sAccountAuthenticator = getAuthenticator(); |
|
35 } |
|
36 |
|
37 @Override |
|
38 public IBinder onBind(Intent intent) { |
|
39 if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) { |
|
40 return getAuthenticator().getIBinder(); |
|
41 } |
|
42 return null; |
|
43 } |
|
44 |
|
45 private SyncAccountAuthenticator getAuthenticator() { |
|
46 if (sAccountAuthenticator == null) { |
|
47 sAccountAuthenticator = new SyncAccountAuthenticator(this); |
|
48 } |
|
49 return sAccountAuthenticator; |
|
50 } |
|
51 |
|
52 /** |
|
53 * Generate a "plain" auth token. |
|
54 * <p> |
|
55 * Android caches only the value of the key |
|
56 * <code>AccountManager.KEY_AUTHTOKEN</code>, so if a caller needs the other |
|
57 * keys in this bundle, it needs to invalidate the token (so that the bundle |
|
58 * is re-generated). |
|
59 * |
|
60 * @param context |
|
61 * Android context. |
|
62 * @param account |
|
63 * Android account. |
|
64 * @return a <code>Bundle</code> instance containing a subset of the following |
|
65 * keys: (caller's must check for missing keys) |
|
66 * <ul> |
|
67 * <li><code>AccountManager.KEY_ACCOUNT_TYPE</code>: the Android |
|
68 * Account's type</li> |
|
69 * |
|
70 * <li><code>AccountManager.KEY_ACCOUNT_NAME</code>: the Android |
|
71 * Account's name</li> |
|
72 * |
|
73 * <li><code>AccountManager.KEY_AUTHTOKEN</code>: the Sync account's |
|
74 * password </li> |
|
75 * |
|
76 * <li><code> Constants.OPTION_USERNAME</code>: the Sync account's |
|
77 * hashed username</li> |
|
78 * |
|
79 * <li><code>Constants.OPTION_SERVER</code>: the Sync account's |
|
80 * server</li> |
|
81 * |
|
82 * <li><code> Constants.OPTION_SYNCKEY</code>: the Sync account's |
|
83 * sync key</li> |
|
84 * |
|
85 * </ul> |
|
86 * @throws NetworkErrorException |
|
87 */ |
|
88 public static Bundle getPlainAuthToken(final Context context, final Account account) |
|
89 throws NetworkErrorException { |
|
90 // Extract the username and password from the Account Manager, and ask |
|
91 // the server for an appropriate AuthToken. |
|
92 final AccountManager am = AccountManager.get(context); |
|
93 final String password = am.getPassword(account); |
|
94 if (password == null) { |
|
95 Logger.warn(LOG_TAG, "Returning null bundle for getPlainAuthToken since Account password is null."); |
|
96 return null; |
|
97 } |
|
98 |
|
99 final Bundle result = new Bundle(); |
|
100 |
|
101 // This is a Sync account. |
|
102 result.putString(AccountManager.KEY_ACCOUNT_TYPE, SyncConstants.ACCOUNTTYPE_SYNC); |
|
103 |
|
104 // Server. |
|
105 String serverURL = am.getUserData(account, Constants.OPTION_SERVER); |
|
106 result.putString(Constants.OPTION_SERVER, serverURL); |
|
107 |
|
108 // Full username, before hashing. |
|
109 result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); |
|
110 |
|
111 // Username after hashing. |
|
112 try { |
|
113 String username = Utils.usernameFromAccount(account.name); |
|
114 Logger.pii(LOG_TAG, "Account " + account.name + " hashes to " + username + "."); |
|
115 Logger.debug(LOG_TAG, "Setting username. Null? " + (username == null)); |
|
116 result.putString(Constants.OPTION_USERNAME, username); |
|
117 } catch (NoSuchAlgorithmException e) { |
|
118 // Do nothing. Calling code must check for missing value. |
|
119 Logger.debug(LOG_TAG, "Exception in account lookup: " + e); |
|
120 } catch (UnsupportedEncodingException e) { |
|
121 // Do nothing. Calling code must check for missing value. |
|
122 Logger.debug(LOG_TAG, "Exception in account lookup: " + e); |
|
123 } |
|
124 |
|
125 // Sync key. |
|
126 final String syncKey = am.getUserData(account, Constants.OPTION_SYNCKEY); |
|
127 Logger.debug(LOG_TAG, "Setting sync key. Null? " + (syncKey == null)); |
|
128 result.putString(Constants.OPTION_SYNCKEY, syncKey); |
|
129 |
|
130 // Password. |
|
131 result.putString(AccountManager.KEY_AUTHTOKEN, password); |
|
132 return result; |
|
133 } |
|
134 |
|
135 private static class SyncAccountAuthenticator extends AbstractAccountAuthenticator { |
|
136 private Context mContext; |
|
137 public SyncAccountAuthenticator(Context context) { |
|
138 super(context); |
|
139 mContext = context; |
|
140 } |
|
141 |
|
142 @Override |
|
143 public Bundle addAccount(AccountAuthenticatorResponse response, |
|
144 String accountType, String authTokenType, String[] requiredFeatures, |
|
145 Bundle options) throws NetworkErrorException { |
|
146 Logger.debug(LOG_TAG, "addAccount()"); |
|
147 final Intent intent = new Intent(mContext, SetupSyncActivity.class); |
|
148 intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, |
|
149 response); |
|
150 intent.putExtra("accountType", SyncConstants.ACCOUNTTYPE_SYNC); |
|
151 intent.putExtra(Constants.INTENT_EXTRA_IS_SETUP, true); |
|
152 |
|
153 final Bundle result = new Bundle(); |
|
154 result.putParcelable(AccountManager.KEY_INTENT, intent); |
|
155 |
|
156 return result; |
|
157 } |
|
158 |
|
159 @Override |
|
160 public Bundle confirmCredentials(AccountAuthenticatorResponse response, |
|
161 Account account, |
|
162 Bundle options) throws NetworkErrorException { |
|
163 Logger.debug(LOG_TAG, "confirmCredentials()"); |
|
164 return null; |
|
165 } |
|
166 |
|
167 @Override |
|
168 public Bundle editProperties(AccountAuthenticatorResponse response, |
|
169 String accountType) { |
|
170 Logger.debug(LOG_TAG, "editProperties"); |
|
171 return null; |
|
172 } |
|
173 |
|
174 @Override |
|
175 public Bundle getAuthToken(AccountAuthenticatorResponse response, |
|
176 Account account, String authTokenType, Bundle options) |
|
177 throws NetworkErrorException { |
|
178 Logger.debug(LOG_TAG, "getAuthToken()"); |
|
179 |
|
180 if (Constants.AUTHTOKEN_TYPE_PLAIN.equals(authTokenType)) { |
|
181 return getPlainAuthToken(mContext, account); |
|
182 } |
|
183 |
|
184 final Bundle result = new Bundle(); |
|
185 result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType"); |
|
186 return result; |
|
187 } |
|
188 |
|
189 @Override |
|
190 public String getAuthTokenLabel(String authTokenType) { |
|
191 Logger.debug(LOG_TAG, "getAuthTokenLabel()"); |
|
192 return null; |
|
193 } |
|
194 |
|
195 @Override |
|
196 public Bundle hasFeatures(AccountAuthenticatorResponse response, |
|
197 Account account, String[] features) throws NetworkErrorException { |
|
198 Logger.debug(LOG_TAG, "hasFeatures()"); |
|
199 return null; |
|
200 } |
|
201 |
|
202 @Override |
|
203 public Bundle updateCredentials(AccountAuthenticatorResponse response, |
|
204 Account account, String authTokenType, Bundle options) |
|
205 throws NetworkErrorException { |
|
206 Logger.debug(LOG_TAG, "updateCredentials()"); |
|
207 return null; |
|
208 } |
|
209 |
|
210 /** |
|
211 * Bug 769745: persist pickled Sync account settings so that we can unpickle |
|
212 * after Fennec is moved to the SD card. |
|
213 * <p> |
|
214 * This is <b>not</b> called when an Android Account is blown away due to |
|
215 * the SD card being unmounted. |
|
216 * <p> |
|
217 * Broadcasting a Firefox intent to version sharing this Android Account is |
|
218 * a terrible hack, but it's better than the catching the generic |
|
219 * "accounts changed" broadcast intent and trying to figure out whether our |
|
220 * Account disappeared. |
|
221 */ |
|
222 @Override |
|
223 public Bundle getAccountRemovalAllowed(final AccountAuthenticatorResponse response, Account account) |
|
224 throws NetworkErrorException { |
|
225 Bundle result = super.getAccountRemovalAllowed(response, account); |
|
226 |
|
227 if (result == null || |
|
228 !result.containsKey(AccountManager.KEY_BOOLEAN_RESULT) || |
|
229 result.containsKey(AccountManager.KEY_INTENT)) { |
|
230 return result; |
|
231 } |
|
232 |
|
233 final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); |
|
234 if (!removalAllowed) { |
|
235 return result; |
|
236 } |
|
237 |
|
238 // Bug 790931: Broadcast a message to all Firefox versions sharing this |
|
239 // Android Account type telling that this Sync Account has been deleted. |
|
240 // |
|
241 // We would really prefer to receive Android's |
|
242 // LOGIN_ACCOUNTS_CHANGED_ACTION broadcast, but that |
|
243 // doesn't include enough information about which Accounts changed to |
|
244 // correctly identify whether a Sync account has been removed (when some |
|
245 // Firefox versions are installed on the SD card). |
|
246 // |
|
247 // Broadcast intents protected with permissions are secure, so it's okay |
|
248 // to include password and sync key, etc. |
|
249 final Intent intent = SyncAccounts.makeSyncAccountDeletedIntent(mContext, AccountManager.get(mContext), account); |
|
250 Logger.info(LOG_TAG, "Account named " + account.name + " being removed; " + |
|
251 "broadcasting secure intent " + intent.getAction() + "."); |
|
252 mContext.sendBroadcast(intent, SyncConstants.PER_ACCOUNT_TYPE_PERMISSION); |
|
253 |
|
254 return result; |
|
255 } |
|
256 } |
|
257 } |