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

Unified Diff: net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java

Issue 1508833003: Fix permission checking for negotiate auth on Android (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Removed the dependent patchset Created 4 years, 11 months 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 side-by-side diff with in-line comments
Download patch
Index: net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java
diff --git a/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java b/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java
index bbc0583d84685c3802eac52ba880fc12649e969b..a965196633f169c1e4b001e07b9484400a2c2061 100644
--- a/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java
+++ b/net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java
@@ -4,6 +4,7 @@
package org.chromium.net;
+import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
@@ -16,6 +17,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
@@ -33,6 +35,26 @@
/**
* Class to get Auth Tokens for HTTP Negotiate authentication (typically used for Kerberos) An
* instance of this class is created for each separate negotiation.
+ *
+ * Please keep the documentation from the chromium.org page (https://goo.gl/46hmKx) in sync.
+ * ================================================================================================
+ * In addition to the error codes that can be forwarded from the authenticator app, the following
+ * errors can be displayed when trying to authenticate a request:
+ *
+ * - ERR_UNEXPECTED: An unexpected error happened and the request has been terminated.
+ *
+ * - ERR_MISSING_AUTH_CREDENTIALS: The account information is not usable. It can be raised for
+ * example if the user did not log in to the authenticator app and no eligible account is found,
+ * if the account information can't be obtained because the current app does not have the
+ * required permissions, or if there is more than one eligible account and we can't obtain a
+ * selection from the user.
+ *
+ * - ERR_MISCONFIGURED_AUTH_ENVIRONMENT: The authentication can't be completed because of some
+ * issues in the configuration of the app. Some permissions may be missing.
+ *
+ * Please search for the "cr_net_auth" tag in logcat to have more information about the cause of
+ * these errors.
+ * ================================================================================================
*/
@JNINamespace("net::android")
public class HttpNegotiateAuthenticator {
@@ -78,22 +100,37 @@ public void run(AccountManagerFuture<Account[]> future) {
try {
accounts = future.getResult();
} catch (OperationCanceledException | AuthenticatorException | IOException e) {
- Log.w(TAG, "Unable to retrieve the results for the getAccounts call.", e);
+ Log.w(TAG, "ERR_UNEXPECTED: Error while attempting to retrieve accounts.", e);
nativeSetResult(mRequestData.nativeResultObject, NetError.ERR_UNEXPECTED, null);
return;
}
- if (accounts.length != 1) {
- Log.w(TAG, "Unable to obtain a unique eligible account for negotiate auth.");
+ if (accounts.length == 0) {
+ Log.w(TAG, "ERR_MISSING_AUTH_CREDENTIALS: No account provided for the kerberos "
+ + "authentication. Please verify the configuration policies and "
+ + "that the CONTACTS runtime permission is granted. ");
nativeSetResult(mRequestData.nativeResultObject,
- NetError.ERR_INVALID_AUTH_CREDENTIALS, null);
+ NetError.ERR_MISSING_AUTH_CREDENTIALS, null);
dgn 2016/01/06 21:13:24 Changed from INVALID to MISSING since we don't hav
return;
}
- if (!hasPermission(ContextUtils.getApplicationContext(),
- "android.permission.USE_CREDENTIALS")) {
+
+ if (accounts.length > 1) {
+ Log.w(TAG, "ERR_MISSING_AUTH_CREDENTIALS: Found %d accounts eligible for the "
+ + "kerberos authentication. Please fix the configuration by "
+ + "providing a single account.",
+ accounts.length);
+ nativeSetResult(mRequestData.nativeResultObject,
+ NetError.ERR_MISSING_AUTH_CREDENTIALS, null);
dgn 2016/01/06 21:13:24 Changed from INVALID to MISSING since the credenti
+ return;
+ }
+
+ if (lacksPermission(ContextUtils.getApplicationContext(),
+ "android.permission.USE_CREDENTIALS", true)) {
+ // Protecting the AccountManager#getAuthToken call.
// API < 23 Requires the USE_CREDENTIALS permission or throws an exception.
- // API >= 23 USE_CREDENTIALS is auto-granted without having to be declared.
- Log.e(TAG, "USE_CREDENTIALS permission not granted. Aborting authentication.");
+ // API >= 23 USE_CREDENTIALS permission is removed
+ Log.e(TAG, "ERR_MISCONFIGURED_AUTH_ENVIRONMENT: USE_CREDENTIALS permission not "
+ + "granted. Aborting authentication.");
nativeSetResult(mRequestData.nativeResultObject,
NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
return;
@@ -119,7 +156,7 @@ public void run(AccountManagerFuture<Bundle> future) {
try {
result = future.getResult();
} catch (OperationCanceledException | AuthenticatorException | IOException e) {
- Log.w(TAG, "Operation did not complete", e);
+ Log.w(TAG, "ERR_UNEXPECTED: Error while attempting to obtain a token.", e);
nativeSetResult(mRequestData.nativeResultObject, NetError.ERR_UNEXPECTED, null);
return;
}
@@ -197,41 +234,12 @@ void getNextAuthToken(final long nativeResultObject, final String principal, Str
HttpNegotiateConstants.KEY_SPNEGO_CONTEXT, mSpnegoContext);
}
requestData.options.putBoolean(HttpNegotiateConstants.KEY_CAN_DELEGATE, canDelegate);
- Handler uiThreadHandler = new Handler(ThreadUtils.getUiThreadLooper());
Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
if (activity == null) {
- // This code path is not as able to get user input as the one that has an activity. It
- // doesn't handle 0 or multiple accounts and will just result in a failure in those
- // cases.
- if (!hasPermission(applicationContext, android.Manifest.permission.GET_ACCOUNTS)) {
- // API < 23 Requires the GET_ACCOUNTS permission or throws an exception.
- // API >= 23 Requires the GET_ACCOUNTS permission or returns only the accounts
- // whose authenticator has a signature matches our app.
- Log.e(TAG, "GET_ACCOUNTS permission not granted. Aborting authentication.");
- nativeSetResult(requestData.nativeResultObject,
- NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
- return;
- }
- requestData.accountManager.getAccountsByTypeAndFeatures(
- mAccountType, features, new GetAccountsCallback(requestData), uiThreadHandler);
+ requestTokenWithoutActivity(applicationContext, requestData, features);
} else {
- // If there is more than one account, it will show the an account picker dialog for
- // each query (e.g. html file, then favicon...)
- // Same if the credentials need to be confirmed.
- // If there is a failure, it will retry automatically.
- if (!hasPermission(applicationContext, "android.permission.MANAGE_ACCOUNTS")) {
- // API < 23 Requires the MANAGE_ACCOUNTS permission.
- // API >= 23 Requires something in the CONTACTS permission group to behave properly.
- // MANAGE_ACCOUNTS is auto-granted on install without having be declared.
- Log.e(TAG, "MANAGE_ACCOUNTS permission not granted. Aborting authentication.");
- nativeSetResult(requestData.nativeResultObject,
- NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
- return;
- }
- requestData.accountManager.getAuthTokenByFeatures(mAccountType,
- requestData.authTokenType, features, activity, null, requestData.options,
- new GetTokenCallback(requestData), uiThreadHandler);
+ requestTokenWithActivity(applicationContext, activity, requestData, features);
}
}
@@ -281,11 +289,86 @@ private void processResult(Bundle result, RequestData requestData) {
result.getString(AccountManager.KEY_AUTHTOKEN));
}
+ /**
+ * Requests an authentication token. If the account is not properly setup, it will result in
+ * a failure.
+ *
+ * @param ctx The application context
+ * @param requestData The object holding the data related to the current request
+ * @param features An array of the account features to require, may be null or empty
+ */
+ private void requestTokenWithoutActivity(
+ Context ctx, RequestData requestData, String[] features) {
+ if (lacksPermission(ctx, Manifest.permission.GET_ACCOUNTS, true /* onlyPreM */)) {
+ // Protecting the AccountManager#getAccountsByTypeAndFeatures call.
+ // API < 23 Requires the GET_ACCOUNTS permission or throws an exception.
+ // API >= 23 Requires the GET_ACCOUNTS permission (CONTACTS permission group) or
+ // returns only the accounts whose authenticator has a signature that
+ // matches our app. Working with this restriction and not requesting
+ // the permission is a valid use case in the context of WebView, so we
+ // don't require it on M+
+ Log.e(TAG, "ERR_MISCONFIGURED_AUTH_ENVIRONMENT: GET_ACCOUNTS permission not "
+ + "granted. Aborting authentication.");
+ nativeSetResult(requestData.nativeResultObject,
+ NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
+ return;
+ }
+ requestData.accountManager.getAccountsByTypeAndFeatures(mAccountType, features,
+ new GetAccountsCallback(requestData), new Handler(ThreadUtils.getUiThreadLooper()));
+ }
+
+ /**
+ * Requests an authentication token. Handles the account selection/creation and the credentials
+ * confirmation if that is needed.
+ * If there is more than one account, it will show an account picker dialog for
+ * each query (e.g. html file, then favicon...)
+ * Same if the credentials need to be confirmed.
+ *
+ * @param ctx The application context
+ * @param activity The Activity context to use for launching new sub-Activities to prompt to
+ * add an account, select an account, and/or enter a password, as necessary;
+ * used only to call startActivity(); should not be null
+ * @param requestData The object holding the data related to the current request
+ * @param features An array of the account features to require, may be null or empty
+ */
+ private void requestTokenWithActivity(
+ Context ctx, Activity activity, RequestData requestData, String[] features) {
+ boolean isPreM = Build.VERSION.SDK_INT < Build.VERSION_CODES.M;
+ String permission = isPreM
+ ? "android.permission.MANAGE_ACCOUNTS"
+ : Manifest.permission.GET_ACCOUNTS;
+
+ // Check if the AccountManager#getAuthTokenByFeatures call can be made.
+ // API < 23 Requires the MANAGE_ACCOUNTS permission.
+ // API >= 23 Requires the GET_ACCOUNTS permission to behave properly. When it's not granted,
+ // accounts not managed by the current application can't be retrieved. Depending
+ // on the authenticator implementation, it might prompt to create an account, but
+ // that won't be saved. This would be a bad user experience, so we also consider
+ // it a failure case.
+ if (lacksPermission(ctx, permission, isPreM)) {
+ Log.e(TAG, "ERR_MISCONFIGURED_AUTH_ENVIRONMENT: %s permission not granted. "
+ + "Aborting authentication", permission);
+ nativeSetResult(requestData.nativeResultObject,
+ NetError.ERR_MISCONFIGURED_AUTH_ENVIRONMENT, null);
+ return;
+ }
+
+ requestData.accountManager.getAuthTokenByFeatures(mAccountType, requestData.authTokenType,
+ features, activity, null, requestData.options, new GetTokenCallback(requestData),
+ new Handler(ThreadUtils.getUiThreadLooper()));
+ }
+
+ /**
+ * Returns whether the current context lacks a given permission. Skips the check on M+ systems
+ * if {@code onlyPreM} is {@code true}, and just returns {@code false}.
+ */
@VisibleForTesting
- boolean hasPermission(Context context, String permission) {
+ boolean lacksPermission(Context context, String permission, boolean onlyPreM) {
+ if (onlyPreM && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) return false;
+
int permissionResult =
context.checkPermission(permission, Process.myPid(), Process.myUid());
- return permissionResult == PackageManager.PERMISSION_GRANTED;
+ return permissionResult != PackageManager.PERMISSION_GRANTED;
}
@VisibleForTesting

Powered by Google App Engine
This is Rietveld 408576698