Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java

Issue 1422693002: Make Android HttpNegotiateAuthenticator work without an activity (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@AppStatusWebview
Patch Set: Fix destroyForJUnitTests Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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;
18 import android.content.pm.PackageManager;
13 import android.os.Bundle; 19 import android.os.Bundle;
14 import android.os.Handler; 20 import android.os.Handler;
21 import android.os.Process;
15 22
16 import org.chromium.base.ApplicationStatus; 23 import org.chromium.base.ApplicationStatus;
24 import org.chromium.base.ContextUtils;
25 import org.chromium.base.Log;
17 import org.chromium.base.ThreadUtils; 26 import org.chromium.base.ThreadUtils;
18 import org.chromium.base.VisibleForTesting; 27 import org.chromium.base.VisibleForTesting;
19 import org.chromium.base.annotations.CalledByNative; 28 import org.chromium.base.annotations.CalledByNative;
20 import org.chromium.base.annotations.JNINamespace; 29 import org.chromium.base.annotations.JNINamespace;
21 30
22 import java.io.IOException; 31 import java.io.IOException;
23 32
24 /** 33 /**
25 * Class to get Auth Tokens for HTTP Negotiate authentication (typically used fo r Kerberos) An 34 * 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. 35 * instance of this class is created for each separate negotiation.
27 */ 36 */
28 @JNINamespace("net::android") 37 @JNINamespace("net::android")
29 public class HttpNegotiateAuthenticator { 38 public class HttpNegotiateAuthenticator {
39 private static final String TAG = "net_auth";
30 private Bundle mSpnegoContext = null; 40 private Bundle mSpnegoContext = null;
31 private final String mAccountType; 41 private final String mAccountType;
32 private AccountManagerFuture<Bundle> mFuture;
33 42
34 private HttpNegotiateAuthenticator(String accountType) { 43 /**
44 * Structure designed to hold the data related to a specific request across the various
45 * callbacks needed to complete it.
46 */
47 static class RequestData {
48 /** Native object to post the result to. */
49 public long nativeResultObject;
50
51 /** Reference to the account manager to use for the various requests. */
52 public AccountManager accountManager;
53
54 /** Authenticator-specific options for the request, used for AccountMana ger#getAuthToken. */
55 public Bundle options;
56
57 /** Desired token type, used for AccountManager#getAuthToken. */
58 public String authTokenType;
59
60 /** Account to fetch an auth token for. */
61 public Account account;
62 }
63
64 /**
65 * Expects to receive a single account as result, and uses that account to r equest a token
66 * from the {@link AccountManager} provided via the {@link RequestData}
67 */
68 @VisibleForTesting
69 class GetAccountsCallback implements AccountManagerCallback<Account[]> {
70 private final RequestData mRequestData;
71 public GetAccountsCallback(RequestData requestData) {
72 mRequestData = requestData;
73 }
74
75 @Override
76 public void run(AccountManagerFuture<Account[]> future) {
77 Account[] accounts;
78 try {
79 accounts = future.getResult();
80 } catch (OperationCanceledException | AuthenticatorException | IOExc eption e) {
81 Log.w(TAG, "Unable to retrieve the results for the getAccounts c all.", e);
82 nativeSetResult(mRequestData.nativeResultObject, NetError.ERR_UN EXPECTED, null);
83 return;
84 }
85
86 if (accounts.length != 1) {
87 Log.w(TAG, "Unable to obtain a unique eligible account for negot iate auth.");
88 nativeSetResult(mRequestData.nativeResultObject,
89 NetError.ERR_INVALID_AUTH_CREDENTIALS, null);
90 return;
91 }
92 if (!hasPermission(ContextUtils.getApplicationContext(),
93 "android.permission.USE_CREDENTIALS")) {
94 // API < 23 Requires the USE_CREDENTIALS permission or throws a n exception.
95 // API >= 23 USE_CREDENTIALS is auto-granted without having to b e declared.
96 Log.e(TAG, "USE_CREDENTIALS permission not granted. Aborting aut hentication.");
97 nativeSetResult(mRequestData.nativeResultObject,
98 NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
99 return;
100 }
101 mRequestData.account = accounts[0];
102 mRequestData.accountManager.getAuthToken(mRequestData.account,
103 mRequestData.authTokenType, mRequestData.options, true /* no tifyAuthFailure */,
104 new GetTokenCallback(mRequestData),
105 new Handler(ThreadUtils.getUiThreadLooper()));
106 }
107 }
108
109 @VisibleForTesting
110 class GetTokenCallback implements AccountManagerCallback<Bundle> {
111 private final RequestData mRequestData;
112 public GetTokenCallback(RequestData requestData) {
113 mRequestData = requestData;
114 }
115
116 @Override
117 public void run(AccountManagerFuture<Bundle> future) {
118 Bundle result;
119 try {
120 result = future.getResult();
121 } catch (OperationCanceledException | AuthenticatorException | IOExc eption e) {
122 Log.w(TAG, "Operation did not complete", e);
123 nativeSetResult(mRequestData.nativeResultObject, NetError.ERR_UN EXPECTED, null);
124 return;
125 }
126
127 if (result.containsKey(AccountManager.KEY_INTENT)) {
128 final Context appContext = ContextUtils.getApplicationContext();
129
130 // We wait for a broadcast that should be sent once the user is done interacting
131 // with the notification
132 // TODO(dgn) We currently hang around if the notification is swi ped away, until
133 // a LOGIN_ACCOUNTS_CHANGED_ACTION filter is received. It might be for something
134 // unrelated then we would wait again here. Maybe we should limi t the number of
135 // retries in some way?
136 BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
137
138 @Override
139 public void onReceive(Context context, Intent intent) {
140 appContext.unregisterReceiver(this);
141 mRequestData.accountManager.getAuthToken(mRequestData.ac count,
142 mRequestData.authTokenType, mRequestData.options ,
143 true /* notifyAuthFailure */, new GetTokenCallba ck(mRequestData),
144 null);
145 }
146
147 };
148 appContext.registerReceiver(broadcastReceiver,
149 new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_A CTION));
150 } else {
151 processResult(result, mRequestData);
152 }
153 }
154 }
155
156 protected HttpNegotiateAuthenticator(String accountType) {
35 assert !android.text.TextUtils.isEmpty(accountType); 157 assert !android.text.TextUtils.isEmpty(accountType);
36 mAccountType = accountType; 158 mAccountType = accountType;
37 } 159 }
38 160
39 /** 161 /**
40 * @param accountType The Android account type to use. 162 * @param accountType The Android account type to use.
41 */ 163 */
42 @VisibleForTesting 164 @VisibleForTesting
43 @CalledByNative 165 @CalledByNative
44 static HttpNegotiateAuthenticator create(String accountType) { 166 static HttpNegotiateAuthenticator create(String accountType) {
45 return new HttpNegotiateAuthenticator(accountType); 167 return new HttpNegotiateAuthenticator(accountType);
46 } 168 }
47 169
48 /** 170 /**
49 * @param nativeResultObject The C++ object used to return the result. For c orrect C++ memory 171 * @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. 172 * management we must call nativeSetResult precisely once with th is object.
51 * @param principal The principal (must be host based). 173 * @param principal The principal (must be host based).
52 * @param authToken The incoming auth token. 174 * @param authToken The incoming auth token.
53 * @param canDelegate True if we can delegate. 175 * @param canDelegate True if we can delegate.
54 */ 176 */
55 @VisibleForTesting 177 @VisibleForTesting
56 @CalledByNative 178 @CalledByNative
57 void getNextAuthToken(final long nativeResultObject, final String principal, String authToken, 179 void getNextAuthToken(final long nativeResultObject, final String principal, String authToken,
58 boolean canDelegate) { 180 boolean canDelegate) {
59 assert principal != null; 181 assert principal != null;
60 String authTokenType = HttpNegotiateConstants.SPNEGO_TOKEN_TYPE_BASE + p rincipal; 182
183 Context applicationContext = ContextUtils.getApplicationContext();
184 RequestData requestData = new RequestData();
185 requestData.authTokenType = HttpNegotiateConstants.SPNEGO_TOKEN_TYPE_BAS E + principal;
186 requestData.accountManager = AccountManager.get(applicationContext);
187 requestData.nativeResultObject = nativeResultObject;
188 String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE};
189
190 requestData.options = new Bundle();
191 if (authToken != null) {
192 requestData.options.putString(
193 HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN, authToken);
194 }
195 if (mSpnegoContext != null) {
196 requestData.options.putBundle(
197 HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, mSpnegoContext);
198 }
199 requestData.options.putBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE, canDelegate);
200 Handler uiThreadHandler = new Handler(ThreadUtils.getUiThreadLooper());
201
61 Activity activity = ApplicationStatus.getLastTrackedFocusedActivity(); 202 Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
62 if (activity == null) { 203 if (activity == null) {
63 nativeSetResult(nativeResultObject, NetError.ERR_UNEXPECTED, null); 204 // This code path is not as able to get user input as the one that h as an activity. It
64 return; 205 // doesn't handle 0 or multiple accounts and will just result in a f ailure in those
206 // cases.
207 if (!hasPermission(applicationContext, android.Manifest.permission.G ET_ACCOUNTS)) {
208 // API < 23 Requires the GET_ACCOUNTS permission or throws an e xception.
209 // API >= 23 Requires the GET_ACCOUNTS permission or returns onl y the accounts
210 // whose authenticator has a signature matches our app .
211 Log.e(TAG, "GET_ACCOUNTS permission not granted. Aborting authen tication.");
212 nativeSetResult(requestData.nativeResultObject,
213 NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
214 return;
215 }
216 requestData.accountManager.getAccountsByTypeAndFeatures(
217 mAccountType, features, new GetAccountsCallback(requestData) , uiThreadHandler);
218 } else {
219 // If there is more than one account, it will show the an account pi cker dialog for
220 // each query (e.g. html file, then favicon...)
221 // Same if the credentials need to be confirmed.
222 // If there is a failure, it will retry automatically.
223 if (!hasPermission(applicationContext, "android.permission.MANAGE_AC COUNTS")) {
224 // API < 23 Requires the MANAGE_ACCOUNTS permission.
225 // API >= 23 Requires something in the CONTACTS permission group to behave properly.
226 // MANAGE_ACCOUNTS is auto-granted on install without having be declared.
227 Log.e(TAG, "MANAGE_ACCOUNTS permission not granted. Aborting aut hentication.");
228 nativeSetResult(requestData.nativeResultObject,
229 NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
230 return;
231 }
232 requestData.accountManager.getAuthTokenByFeatures(mAccountType,
233 requestData.authTokenType, features, activity, null, request Data.options,
234 new GetTokenCallback(requestData), uiThreadHandler);
65 } 235 }
66 AccountManager am = AccountManager.get(activity); 236 }
67 String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE};
68 237
69 Bundle options = new Bundle(); 238 /**
70 239 * Process a result bundle from a completed token request, communicating its content back to
71 if (authToken != null) { 240 * the native code.
72 options.putString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN, au thToken); 241 */
242 private void processResult(Bundle result, RequestData requestData) {
243 mSpnegoContext = result.getBundle(HttpNegotiateConstants.KEY_SPNEGO_CONT EXT);
244 int status;
245 switch (result.getInt(
246 HttpNegotiateConstants.KEY_SPNEGO_RESULT, HttpNegotiateConstants .ERR_UNEXPECTED)) {
247 case HttpNegotiateConstants.OK:
248 status = 0;
249 break;
250 case HttpNegotiateConstants.ERR_UNEXPECTED:
251 status = NetError.ERR_UNEXPECTED;
252 break;
253 case HttpNegotiateConstants.ERR_ABORTED:
254 status = NetError.ERR_ABORTED;
255 break;
256 case HttpNegotiateConstants.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS:
257 status = NetError.ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS;
258 break;
259 case HttpNegotiateConstants.ERR_INVALID_RESPONSE:
260 status = NetError.ERR_INVALID_RESPONSE;
261 break;
262 case HttpNegotiateConstants.ERR_INVALID_AUTH_CREDENTIALS:
263 status = NetError.ERR_INVALID_AUTH_CREDENTIALS;
264 break;
265 case HttpNegotiateConstants.ERR_UNSUPPORTED_AUTH_SCHEME:
266 status = NetError.ERR_UNSUPPORTED_AUTH_SCHEME;
267 break;
268 case HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS:
269 status = NetError.ERR_MISSING_AUTH_CREDENTIALS;
270 break;
271 case HttpNegotiateConstants.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS :
272 status = NetError.ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
273 break;
274 case HttpNegotiateConstants.ERR_MALFORMED_IDENTITY:
275 status = NetError.ERR_MALFORMED_IDENTITY;
276 break;
277 default:
278 status = NetError.ERR_UNEXPECTED;
73 } 279 }
74 if (mSpnegoContext != null) { 280 nativeSetResult(requestData.nativeResultObject, status,
75 options.putBundle(HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, mSpnego Context); 281 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 } 282 }
135 283
136 @VisibleForTesting 284 @VisibleForTesting
285 boolean hasPermission(Context context, String permission) {
286 int permissionResult =
287 context.checkPermission(permission, Process.myPid(), Process.myU id());
288 return permissionResult == PackageManager.PERMISSION_GRANTED;
289 }
290
291 @VisibleForTesting
137 native void nativeSetResult( 292 native void nativeSetResult(
138 long nativeJavaNegotiateResultWrapper, int status, String authToken) ; 293 long nativeJavaNegotiateResultWrapper, int status, String authToken) ;
139 } 294 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698