OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.net; | 5 package org.chromium.net; |
6 | 6 |
7 import android.accounts.Account; | |
7 import android.accounts.AccountManager; | 8 import android.accounts.AccountManager; |
8 import android.accounts.AccountManagerCallback; | 9 import android.accounts.AccountManagerCallback; |
9 import android.accounts.AccountManagerFuture; | 10 import android.accounts.AccountManagerFuture; |
10 import android.accounts.AuthenticatorException; | 11 import android.accounts.AuthenticatorException; |
11 import android.accounts.OperationCanceledException; | 12 import android.accounts.OperationCanceledException; |
12 import android.app.Activity; | 13 import android.app.Activity; |
14 import android.content.BroadcastReceiver; | |
15 import android.content.Context; | |
16 import android.content.Intent; | |
17 import android.content.IntentFilter; | |
13 import android.os.Bundle; | 18 import android.os.Bundle; |
14 import android.os.Handler; | 19 import android.os.Handler; |
15 | 20 |
16 import org.chromium.base.ApplicationStatus; | 21 import org.chromium.base.ApplicationStatus; |
22 import org.chromium.base.Log; | |
17 import org.chromium.base.ThreadUtils; | 23 import org.chromium.base.ThreadUtils; |
18 import org.chromium.base.VisibleForTesting; | 24 import org.chromium.base.VisibleForTesting; |
19 import org.chromium.base.annotations.CalledByNative; | 25 import org.chromium.base.annotations.CalledByNative; |
20 import org.chromium.base.annotations.JNINamespace; | 26 import org.chromium.base.annotations.JNINamespace; |
21 | 27 |
22 import java.io.IOException; | 28 import java.io.IOException; |
23 | 29 |
24 /** | 30 /** |
25 * Class to get Auth Tokens for HTTP Negotiate authentication (typically used fo r Kerberos) An | 31 * Class to get Auth Tokens for HTTP Negotiate authentication (typically used fo r Kerberos) An |
26 * instance of this class is created for each separate negotiation. | 32 * instance of this class is created for each separate negotiation. |
27 */ | 33 */ |
28 @JNINamespace("net::android") | 34 @JNINamespace("net::android") |
29 public class HttpNegotiateAuthenticator { | 35 public class HttpNegotiateAuthenticator { |
36 private static final String TAG = "net_auth"; | |
30 private Bundle mSpnegoContext = null; | 37 private Bundle mSpnegoContext = null; |
31 private final String mAccountType; | 38 private final String mAccountType; |
32 private AccountManagerFuture<Bundle> mFuture; | |
33 | 39 |
34 private HttpNegotiateAuthenticator(String accountType) { | 40 /** |
41 * Structure designed to hold the data related to a specific request across the various | |
42 * callbacks needed to complete it. | |
43 */ | |
44 static class RequestData { | |
45 /** Native object to post the result to. */ | |
46 public long nativeResultObject; | |
47 | |
48 /** Reference to the account manager to use for the various requests. */ | |
49 public AccountManager accountManager; | |
50 | |
51 /** Authenticator-specific options for the request, used for AccountMana ger#getAuthToken. */ | |
52 public Bundle options; | |
53 | |
54 /** Desired token type, used for AccountManager#getAuthToken. */ | |
55 public String authTokenType; | |
56 | |
57 /** Account to fetch an auth token for. */ | |
58 public Account account; | |
59 } | |
60 | |
61 /** | |
62 * Expects to receive a single account as result, and uses that account to r equest a token | |
63 * from the {@link AccountManager} provided via the {@link RequestData} | |
64 */ | |
65 @VisibleForTesting | |
66 class GetAccountsCallback implements AccountManagerCallback<Account[]> { | |
67 private final RequestData mRequestData; | |
68 public GetAccountsCallback(RequestData requestData) { | |
69 mRequestData = requestData; | |
70 } | |
71 | |
72 @Override | |
73 public void run(AccountManagerFuture<Account[]> future) { | |
74 Account[] accounts; | |
75 try { | |
76 accounts = future.getResult(); | |
77 } catch (OperationCanceledException | AuthenticatorException | IOExc eption e) { | |
78 notifyFailure("Unable to retrieve the results for the getAccount s call.", e, | |
79 mRequestData); | |
80 return; | |
81 } | |
82 | |
83 if (accounts.length != 1) { | |
asanka
2015/11/03 17:45:51
Is there no way to select an account in this case?
dgn
2015/11/05 14:39:14
There isn't any. Showing any kind of UI requires h
| |
84 // TODO(dgn) Maybe should fail with an error like ERR_INVALID_AU TH_CREDENTIALS | |
85 // instead of UNEXPECTED? | |
asanka
2015/11/03 17:45:51
Yeah. ERR_INVALID_AUTH_CREDENTIALS would be better
dgn
2015/11/05 14:39:14
Done.
| |
86 notifyFailure("Unable to obtain a unique eligible account for ne gotiate auth.", | |
87 null, mRequestData); | |
88 return; | |
89 } | |
90 | |
91 mRequestData.account = accounts[0]; | |
92 mRequestData.accountManager.getAuthToken(mRequestData.account, | |
93 mRequestData.authTokenType, mRequestData.options, true /* no tifyAuthFailure */, | |
94 new GetTokenCallback(mRequestData), | |
95 new Handler(ThreadUtils.getUiThreadLooper())); | |
96 } | |
97 } | |
98 | |
99 @VisibleForTesting | |
100 class GetTokenCallback implements AccountManagerCallback<Bundle> { | |
101 private final RequestData mRequestData; | |
102 public GetTokenCallback(RequestData requestData) { | |
103 mRequestData = requestData; | |
104 } | |
105 | |
106 @Override | |
107 public void run(AccountManagerFuture<Bundle> future) { | |
108 Bundle result; | |
109 try { | |
110 result = future.getResult(); | |
111 } catch (OperationCanceledException | AuthenticatorException | IOExc eption e) { | |
112 notifyFailure("Operation did not complete", e, mRequestData); | |
113 return; | |
114 } | |
115 | |
116 if (result.containsKey(AccountManager.KEY_INTENT)) { | |
117 final Context appContext = ApplicationStatus.getApplicationConte xt(); | |
dgn
2015/10/30 12:38:56
Note: torne@ is adding a replacement for this, I'l
aberent
2015/10/30 16:38:10
nit: Maybe add a TODO if this is landing first.
dgn
2015/10/30 17:04:40
I'll wait. It's broken otherwise, even though the
| |
118 | |
119 // We wait for a broadcast that should be sent once the user is done interacting | |
120 // with the notification | |
121 // TODO(dgn) We currently hang around if the notification is swi ped away, until | |
122 // a LOGIN_ACCOUNTS_CHANGED_ACTION filter is received. It might be for something | |
123 // unrelated then we would wait again here. Maybe we should limi t the number of | |
124 // retries in some way? | |
125 BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { | |
126 | |
127 @Override | |
128 public void onReceive(Context context, Intent intent) { | |
129 appContext.unregisterReceiver(this); | |
130 mRequestData.accountManager.getAuthToken(mRequestData.ac count, | |
131 mRequestData.authTokenType, mRequestData.options , | |
132 true /* notifyAuthFailure */, new GetTokenCallba ck(mRequestData), | |
133 null); | |
134 } | |
135 | |
136 }; | |
137 appContext.registerReceiver(broadcastReceiver, | |
138 new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_A CTION)); | |
139 } else { | |
140 setResult(result, mRequestData); | |
141 } | |
142 } | |
143 } | |
144 | |
145 protected HttpNegotiateAuthenticator(String accountType) { | |
35 assert !android.text.TextUtils.isEmpty(accountType); | 146 assert !android.text.TextUtils.isEmpty(accountType); |
36 mAccountType = accountType; | 147 mAccountType = accountType; |
37 } | 148 } |
38 | 149 |
39 /** | 150 /** |
40 * @param accountType The Android account type to use. | 151 * @param accountType The Android account type to use. |
41 */ | 152 */ |
42 @VisibleForTesting | 153 @VisibleForTesting |
43 @CalledByNative | 154 @CalledByNative |
44 static HttpNegotiateAuthenticator create(String accountType) { | 155 static HttpNegotiateAuthenticator create(String accountType) { |
45 return new HttpNegotiateAuthenticator(accountType); | 156 return new HttpNegotiateAuthenticator(accountType); |
46 } | 157 } |
47 | 158 |
48 /** | 159 /** |
49 * @param nativeResultObject The C++ object used to return the result. For c orrect C++ memory | 160 * @param nativeResultObject The C++ object used to return the result. For c orrect C++ memory |
50 * management we must call nativeSetResult precisely once with th is object. | 161 * management we must call nativeSetResult precisely once with th is object. |
51 * @param principal The principal (must be host based). | 162 * @param principal The principal (must be host based). |
52 * @param authToken The incoming auth token. | 163 * @param authToken The incoming auth token. |
53 * @param canDelegate True if we can delegate. | 164 * @param canDelegate True if we can delegate. |
54 */ | 165 */ |
55 @VisibleForTesting | 166 @VisibleForTesting |
56 @CalledByNative | 167 @CalledByNative |
57 void getNextAuthToken(final long nativeResultObject, final String principal, String authToken, | 168 void getNextAuthToken(final long nativeResultObject, final String principal, String authToken, |
58 boolean canDelegate) { | 169 boolean canDelegate) { |
59 assert principal != null; | 170 assert principal != null; |
60 String authTokenType = HttpNegotiateConstants.SPNEGO_TOKEN_TYPE_BASE + p rincipal; | 171 |
172 RequestData requestData = new RequestData(); | |
173 requestData.authTokenType = HttpNegotiateConstants.SPNEGO_TOKEN_TYPE_BAS E + principal; | |
174 requestData.accountManager = AccountManager.get(ApplicationStatus.getApp licationContext()); | |
175 requestData.nativeResultObject = nativeResultObject; | |
176 String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE}; | |
177 | |
178 requestData.options = new Bundle(); | |
179 if (authToken != null) { | |
180 requestData.options.putString( | |
181 HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN, authToken); | |
182 } | |
183 if (mSpnegoContext != null) { | |
184 requestData.options.putBundle( | |
185 HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, mSpnegoContext); | |
186 } | |
187 requestData.options.putBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE, canDelegate); | |
188 Handler uiThreadHandler = new Handler(ThreadUtils.getUiThreadLooper()); | |
189 | |
61 Activity activity = ApplicationStatus.getLastTrackedFocusedActivity(); | 190 Activity activity = ApplicationStatus.getLastTrackedFocusedActivity(); |
62 if (activity == null) { | 191 if (activity == null) { |
63 nativeSetResult(nativeResultObject, NetError.ERR_UNEXPECTED, null); | 192 // This code path is not as able to get user input as the one that h as an activity. It |
64 return; | 193 // doesn't handle 0 or multiple accounts and will just result in a f ailure in those |
194 // cases. | |
195 requestData.accountManager.getAccountsByTypeAndFeatures( | |
196 mAccountType, features, new GetAccountsCallback(requestData) , uiThreadHandler); | |
197 } else { | |
198 // Behavior in Chrome | |
199 // If there is more than one account, it will show the disambig for each query | |
asanka
2015/11/03 17:45:51
expn disambig
dgn
2015/11/06 16:51:48
Done.
| |
200 // (e.g. main page, then favicon ...) | |
201 // Same if the credentials need to be confirmed/ | |
202 // If there is a failure, it will retry automatically. | |
203 requestData.accountManager.getAuthTokenByFeatures(mAccountType, | |
204 requestData.authTokenType, features, activity, null, request Data.options, | |
205 new GetTokenCallback(requestData), uiThreadHandler); | |
65 } | 206 } |
66 AccountManager am = AccountManager.get(activity); | 207 } |
67 String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE}; | |
68 | 208 |
69 Bundle options = new Bundle(); | 209 private void notifyFailure(String message, Exception e, RequestData requestD ata) { |
210 Log.w(TAG, message, e); | |
211 nativeSetResult(requestData.nativeResultObject, NetError.ERR_UNEXPECTED, null); | |
212 } | |
70 | 213 |
71 if (authToken != null) { | 214 private void setResult(Bundle result, RequestData requestData) { |
72 options.putString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN, au thToken); | 215 mSpnegoContext = result.getBundle(HttpNegotiateConstants.KEY_SPNEGO_CONT EXT); |
216 int status; | |
217 switch (result.getInt( | |
218 HttpNegotiateConstants.KEY_SPNEGO_RESULT, HttpNegotiateConstants .ERR_UNEXPECTED)) { | |
219 case HttpNegotiateConstants.OK: | |
asanka
2015/11/03 17:45:51
How permanent are the error codes in HttpNegotiate
dgn
2015/11/05 14:39:14
They are supposed to not change as 3p devs will us
asanka
2015/11/05 17:41:32
To give some background, the net::Error values wer
dgn
2015/11/06 16:51:48
Moved this discussion to http://crbug.com/552414.
| |
220 status = 0; | |
221 break; | |
222 case HttpNegotiateConstants.ERR_UNEXPECTED: | |
223 status = NetError.ERR_UNEXPECTED; | |
224 break; | |
225 case HttpNegotiateConstants.ERR_ABORTED: | |
226 status = NetError.ERR_ABORTED; | |
227 break; | |
228 case HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS: | |
229 status = NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
230 break; | |
231 case HttpNegotiateConstants.ERR_INVALID_RESPONSE: | |
232 status = NetError.ERR_INVALID_RESPONSE; | |
233 break; | |
234 case HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS: | |
235 status = NetError.ERR_INVALID_AUTH_CREDENTIALS; | |
236 break; | |
237 case HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME: | |
238 status = NetError.ERR_UNSUPPORTED_AUTH_SCHEME; | |
239 break; | |
240 case HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS: | |
241 status = NetError.ERR_MISSING_AUTH_CREDENTIALS; | |
242 break; | |
243 case HttpNegotiateConstants.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS : | |
244 status = NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; | |
245 break; | |
246 case HttpNegotiateConstants.ERR_MALFORMED_IDENTITY: | |
247 status = NetError.ERR_MALFORMED_IDENTITY; | |
248 break; | |
249 default: | |
250 status = NetError.ERR_UNEXPECTED; | |
73 } | 251 } |
74 if (mSpnegoContext != null) { | 252 nativeSetResult(requestData.nativeResultObject, status, |
75 options.putBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, mSpnego Context); | 253 result.getString(AccountManager.KEY_AUTHTOKEN)); |
76 } | |
77 options.putBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE, canDelegate) ; | |
78 | |
79 mFuture = am.getAuthTokenByFeatures(mAccountType, authTokenType, feature s, activity, null, | |
80 options, new AccountManagerCallback<Bundle>() { | |
81 | |
82 @Override | |
83 public void run(AccountManagerFuture<Bundle> future) { | |
84 try { | |
85 Bundle result = future.getResult(); | |
86 mSpnegoContext = | |
87 result.getBundle(HttpNegotiateConstants.KEY_ SPNEGO_CONTEXT); | |
88 int status; | |
89 switch (result.getInt(HttpNegotiateConstants.KEY_SPN EGO_RESULT, | |
90 HttpNegotiateConstants.ERR_UNEXPECTED)) { | |
91 case HttpNegotiateConstants.OK: | |
92 status = 0; | |
93 break; | |
94 case HttpNegotiateConstants.ERR_UNEXPECTED: | |
95 status = NetError.ERR_UNEXPECTED; | |
96 break; | |
97 case HttpNegotiateConstants.ERR_ABORTED: | |
98 status = NetError.ERR_ABORTED; | |
99 break; | |
100 case HttpNegotiateConstants.ERR_UNEXPECTED_SECUR ITY_LIBRARY_STATUS: | |
101 status = NetError.ERR_UNEXPECTED_SECURITY_LI BRARY_STATUS; | |
102 break; | |
103 case HttpNegotiateConstants.ERR_INVALID_RESPONSE : | |
104 status = NetError.ERR_INVALID_RESPONSE; | |
105 break; | |
106 case HttpNegotiateConstants.ERR_INVALID_AUTH_CRE DENTIALS: | |
107 status = NetError.ERR_INVALID_AUTH_CREDENTIA LS; | |
108 break; | |
109 case HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH _SCHEME: | |
110 status = NetError.ERR_UNSUPPORTED_AUTH_SCHEM E; | |
111 break; | |
112 case HttpNegotiateConstants.ERR_MISSING_AUTH_CRE DENTIALS: | |
113 status = NetError.ERR_MISSING_AUTH_CREDENTIA LS; | |
114 break; | |
115 case HttpNegotiateConstants | |
116 .ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATU S: | |
117 status = NetError.ERR_UNDOCUMENTED_SECURITY_ LIBRARY_STATUS; | |
118 break; | |
119 case HttpNegotiateConstants.ERR_MALFORMED_IDENTI TY: | |
120 status = NetError.ERR_MALFORMED_IDENTITY; | |
121 break; | |
122 default: | |
123 status = NetError.ERR_UNEXPECTED; | |
124 } | |
125 nativeSetResult(nativeResultObject, status, | |
126 result.getString(AccountManager.KEY_AUTHTOKE N)); | |
127 } catch (OperationCanceledException | AuthenticatorExcep tion | |
128 | IOException e) { | |
129 nativeSetResult(nativeResultObject, NetError.ERR_ABO RTED, null); | |
130 } | |
131 } | |
132 | |
133 }, new Handler(ThreadUtils.getUiThreadLooper())); | |
134 } | 254 } |
135 | 255 |
136 @VisibleForTesting | 256 @VisibleForTesting |
137 native void nativeSetResult( | 257 native void nativeSetResult( |
138 long nativeJavaNegotiateResultWrapper, int status, String authToken) ; | 258 long nativeJavaNegotiateResultWrapper, int status, String authToken) ; |
139 } | 259 } |
OLD | NEW |