Blame | Last modification | View Log | RSS feed
/*** Copyright 2010-present Facebook.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.facebook;import android.Manifest;import android.app.Activity;import android.content.ActivityNotFoundException;import android.content.Context;import android.content.Intent;import android.content.SharedPreferences;import android.content.pm.PackageManager;import android.os.Bundle;import android.text.TextUtils;import android.webkit.CookieSyncManager;import com.facebook.android.R;import com.facebook.internal.AnalyticsEvents;import com.facebook.internal.NativeProtocol;import com.facebook.internal.ServerProtocol;import com.facebook.internal.Utility;import com.facebook.model.GraphUser;import com.facebook.widget.WebDialog;import org.json.JSONException;import org.json.JSONObject;import java.io.Serializable;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;class AuthorizationClient implements Serializable {private static final long serialVersionUID = 1L;private static final String TAG = "Facebook-AuthorizationClient";private static final String WEB_VIEW_AUTH_HANDLER_STORE ="com.facebook.AuthorizationClient.WebViewAuthHandler.TOKEN_STORE_KEY";private static final String WEB_VIEW_AUTH_HANDLER_TOKEN_KEY = "TOKEN";// Constants for logging login-related data. Some of these are only used by Session, but grouped here for// maintainability.private static final String EVENT_NAME_LOGIN_METHOD_START = "fb_mobile_login_method_start";private static final String EVENT_NAME_LOGIN_METHOD_COMPLETE = "fb_mobile_login_method_complete";private static final String EVENT_PARAM_METHOD_RESULT_SKIPPED = "skipped";static final String EVENT_NAME_LOGIN_START = "fb_mobile_login_start";static final String EVENT_NAME_LOGIN_COMPLETE = "fb_mobile_login_complete";// Note: to ensure stability of column mappings across the four different event types, we prepend a column// index to each name, and we log all columns with all events, even if they are empty.static final String EVENT_PARAM_AUTH_LOGGER_ID = "0_auth_logger_id";static final String EVENT_PARAM_TIMESTAMP = "1_timestamp_ms";static final String EVENT_PARAM_LOGIN_RESULT = "2_result";static final String EVENT_PARAM_METHOD = "3_method";static final String EVENT_PARAM_ERROR_CODE = "4_error_code";static final String EVENT_PARAM_ERROR_MESSAGE = "5_error_message";static final String EVENT_PARAM_EXTRAS = "6_extras";static final String EVENT_EXTRAS_TRY_LOGIN_ACTIVITY = "try_login_activity";static final String EVENT_EXTRAS_TRY_LEGACY = "try_legacy";static final String EVENT_EXTRAS_LOGIN_BEHAVIOR = "login_behavior";static final String EVENT_EXTRAS_REQUEST_CODE = "request_code";static final String EVENT_EXTRAS_IS_LEGACY = "is_legacy";static final String EVENT_EXTRAS_PERMISSIONS = "permissions";static final String EVENT_EXTRAS_DEFAULT_AUDIENCE = "default_audience";static final String EVENT_EXTRAS_MISSING_INTERNET_PERMISSION = "no_internet_permission";static final String EVENT_EXTRAS_NOT_TRIED = "not_tried";static final String EVENT_EXTRAS_NEW_PERMISSIONS = "new_permissions";List<AuthHandler> handlersToTry;AuthHandler currentHandler;transient Context context;transient StartActivityDelegate startActivityDelegate;transient OnCompletedListener onCompletedListener;transient BackgroundProcessingListener backgroundProcessingListener;transient boolean checkedInternetPermission;AuthorizationRequest pendingRequest;Map<String, String> loggingExtras;private transient AppEventsLogger appEventsLogger;interface OnCompletedListener {void onCompleted(Result result);}interface BackgroundProcessingListener {void onBackgroundProcessingStarted();void onBackgroundProcessingStopped();}interface StartActivityDelegate {public void startActivityForResult(Intent intent, int requestCode);public Activity getActivityContext();}void setContext(final Context context) {this.context = context;// We rely on individual requests to tell us how to start an activity.startActivityDelegate = null;}void setContext(final Activity activity) {this.context = activity;// If we are used in the context of an activity, we will always use that activity to// call startActivityForResult.startActivityDelegate = new StartActivityDelegate() {@Overridepublic void startActivityForResult(Intent intent, int requestCode) {activity.startActivityForResult(intent, requestCode);}@Overridepublic Activity getActivityContext() {return activity;}};}void startOrContinueAuth(AuthorizationRequest request) {if (getInProgress()) {continueAuth();} else {authorize(request);}}void authorize(AuthorizationRequest request) {if (request == null) {return;}if (pendingRequest != null) {throw new FacebookException("Attempted to authorize while a request is pending.");}if (request.needsNewTokenValidation() && !checkInternetPermission()) {// We're going to need INTERNET permission later and don't have it, so fail early.return;}pendingRequest = request;handlersToTry = getHandlerTypes(request);tryNextHandler();}void continueAuth() {if (pendingRequest == null || currentHandler == null) {throw new FacebookException("Attempted to continue authorization without a pending request.");}if (currentHandler.needsRestart()) {currentHandler.cancel();tryCurrentHandler();}}boolean getInProgress() {return pendingRequest != null && currentHandler != null;}void cancelCurrentHandler() {if (currentHandler != null) {currentHandler.cancel();}}boolean onActivityResult(int requestCode, int resultCode, Intent data) {if (pendingRequest != null && requestCode == pendingRequest.getRequestCode()) {return currentHandler.onActivityResult(requestCode, resultCode, data);}return false;}private List<AuthHandler> getHandlerTypes(AuthorizationRequest request) {ArrayList<AuthHandler> handlers = new ArrayList<AuthHandler>();final SessionLoginBehavior behavior = request.getLoginBehavior();if (behavior.allowsKatanaAuth()) {if (!request.isLegacy()) {handlers.add(new GetTokenAuthHandler());}handlers.add(new KatanaProxyAuthHandler());}if (behavior.allowsWebViewAuth()) {handlers.add(new WebViewAuthHandler());}return handlers;}boolean checkInternetPermission() {if (checkedInternetPermission) {return true;}int permissionCheck = checkPermission(Manifest.permission.INTERNET);if (permissionCheck != PackageManager.PERMISSION_GRANTED) {String errorType = context.getString(R.string.com_facebook_internet_permission_error_title);String errorDescription = context.getString(R.string.com_facebook_internet_permission_error_message);complete(Result.createErrorResult(pendingRequest, errorType, errorDescription));return false;}checkedInternetPermission = true;return true;}void tryNextHandler() {if (currentHandler != null) {logAuthorizationMethodComplete(currentHandler.getNameForLogging(), EVENT_PARAM_METHOD_RESULT_SKIPPED,null, null, currentHandler.methodLoggingExtras);}while (handlersToTry != null && !handlersToTry.isEmpty()) {currentHandler = handlersToTry.remove(0);boolean started = tryCurrentHandler();if (started) {return;}}if (pendingRequest != null) {// We went through all handlers without successfully attempting an auth.completeWithFailure();}}private void completeWithFailure() {complete(Result.createErrorResult(pendingRequest, "Login attempt failed.", null));}private void addLoggingExtra(String key, String value, boolean accumulate) {if (loggingExtras == null) {loggingExtras = new HashMap<String, String>();}if (loggingExtras.containsKey(key) && accumulate) {value = loggingExtras.get(key) + "," + value;}loggingExtras.put(key, value);}boolean tryCurrentHandler() {if (currentHandler.needsInternetPermission() && !checkInternetPermission()) {addLoggingExtra(EVENT_EXTRAS_MISSING_INTERNET_PERMISSION, AppEventsConstants.EVENT_PARAM_VALUE_YES,false);return false;}boolean tried = currentHandler.tryAuthorize(pendingRequest);if (tried) {logAuthorizationMethodStart(currentHandler.getNameForLogging());} else {// We didn't try it, so we don't get any other completion notification -- log that we skipped it.addLoggingExtra(EVENT_EXTRAS_NOT_TRIED, currentHandler.getNameForLogging(), true);}return tried;}void completeAndValidate(Result outcome) {// Do we need to validate a successful result (as in the case of a reauth)?if (outcome.token != null && pendingRequest.needsNewTokenValidation()) {validateSameFbidAndFinish(outcome);} else {// We're done, just notify the listener.complete(outcome);}}void complete(Result outcome) {// This might be null if, for some reason, none of the handlers were successfully tried (in which case// we already logged that).if (currentHandler != null) {logAuthorizationMethodComplete(currentHandler.getNameForLogging(), outcome,currentHandler.methodLoggingExtras);}if (loggingExtras != null) {// Pass this back to the caller for logging at the aggregate level.outcome.loggingExtras = loggingExtras;}handlersToTry = null;currentHandler = null;pendingRequest = null;loggingExtras = null;notifyOnCompleteListener(outcome);}OnCompletedListener getOnCompletedListener() {return onCompletedListener;}void setOnCompletedListener(OnCompletedListener onCompletedListener) {this.onCompletedListener = onCompletedListener;}BackgroundProcessingListener getBackgroundProcessingListener() {return backgroundProcessingListener;}void setBackgroundProcessingListener(BackgroundProcessingListener backgroundProcessingListener) {this.backgroundProcessingListener = backgroundProcessingListener;}StartActivityDelegate getStartActivityDelegate() {if (startActivityDelegate != null) {return startActivityDelegate;} else if (pendingRequest != null) {// Wrap the request's delegate in our own.return new StartActivityDelegate() {@Overridepublic void startActivityForResult(Intent intent, int requestCode) {pendingRequest.getStartActivityDelegate().startActivityForResult(intent, requestCode);}@Overridepublic Activity getActivityContext() {return pendingRequest.getStartActivityDelegate().getActivityContext();}};}return null;}int checkPermission(String permission) {return context.checkCallingOrSelfPermission(permission);}void validateSameFbidAndFinish(Result pendingResult) {if (pendingResult.token == null) {throw new FacebookException("Can't validate without a token");}RequestBatch batch = createReauthValidationBatch(pendingResult);notifyBackgroundProcessingStart();batch.executeAsync();}RequestBatch createReauthValidationBatch(final Result pendingResult) {// We need to ensure that the token we got represents the same fbid as the old one. We issue// a "me" request using the current token, a "me" request using the new token, and a "me/permissions"// request using the current token to get the permissions of the user.final ArrayList<String> fbids = new ArrayList<String>();final ArrayList<String> grantedPermissions = new ArrayList<String>();final ArrayList<String> declinedPermissions = new ArrayList<String>();final String newToken = pendingResult.token.getToken();Request.Callback meCallback = new Request.Callback() {@Overridepublic void onCompleted(Response response) {try {GraphUser user = response.getGraphObjectAs(GraphUser.class);if (user != null) {fbids.add(user.getId());}} catch (Exception ex) {}}};String validateSameFbidAsToken = pendingRequest.getPreviousAccessToken();Request requestCurrentTokenMe = createGetProfileIdRequest(validateSameFbidAsToken);requestCurrentTokenMe.setCallback(meCallback);Request requestNewTokenMe = createGetProfileIdRequest(newToken);requestNewTokenMe.setCallback(meCallback);Request requestCurrentTokenPermissions = createGetPermissionsRequest(validateSameFbidAsToken);requestCurrentTokenPermissions.setCallback(new Request.Callback() {@Overridepublic void onCompleted(Response response) {try {Session.PermissionsPair permissionsPair = Session.handlePermissionResponse(response);if (permissionsPair != null) {grantedPermissions.addAll(permissionsPair.getGrantedPermissions());declinedPermissions.addAll(permissionsPair.getDeclinedPermissions());}} catch (Exception ex) {}}});RequestBatch batch = new RequestBatch(requestCurrentTokenMe, requestNewTokenMe,requestCurrentTokenPermissions);batch.setBatchApplicationId(pendingRequest.getApplicationId());batch.addCallback(new RequestBatch.Callback() {@Overridepublic void onBatchCompleted(RequestBatch batch) {try {Result result = null;if (fbids.size() == 2 && fbids.get(0) != null && fbids.get(1) != null &&fbids.get(0).equals(fbids.get(1))) {// Modify the token to have the right permission set.AccessToken tokenWithPermissions = AccessToken.createFromTokenWithRefreshedPermissions(pendingResult.token,grantedPermissions, declinedPermissions);result = Result.createTokenResult(pendingRequest, tokenWithPermissions);} else {result = Result.createErrorResult(pendingRequest, "User logged in as different Facebook user.", null);}complete(result);} catch (Exception ex) {complete(Result.createErrorResult(pendingRequest, "Caught exception", ex.getMessage()));} finally {notifyBackgroundProcessingStop();}}});return batch;}Request createGetPermissionsRequest(String accessToken) {Bundle parameters = new Bundle();parameters.putString("access_token", accessToken);return new Request(null, "me/permissions", parameters, HttpMethod.GET, null);}Request createGetProfileIdRequest(String accessToken) {Bundle parameters = new Bundle();parameters.putString("fields", "id");parameters.putString("access_token", accessToken);return new Request(null, "me", parameters, HttpMethod.GET, null);}private AppEventsLogger getAppEventsLogger() {if (appEventsLogger == null || !appEventsLogger.getApplicationId().equals(pendingRequest.getApplicationId())) {appEventsLogger = AppEventsLogger.newLogger(context, pendingRequest.getApplicationId());}return appEventsLogger;}private void notifyOnCompleteListener(Result outcome) {if (onCompletedListener != null) {onCompletedListener.onCompleted(outcome);}}private void notifyBackgroundProcessingStart() {if (backgroundProcessingListener != null) {backgroundProcessingListener.onBackgroundProcessingStarted();}}private void notifyBackgroundProcessingStop() {if (backgroundProcessingListener != null) {backgroundProcessingListener.onBackgroundProcessingStopped();}}private void logAuthorizationMethodStart(String method) {Bundle bundle = newAuthorizationLoggingBundle(pendingRequest.getAuthId());bundle.putLong(EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());bundle.putString(EVENT_PARAM_METHOD, method);getAppEventsLogger().logSdkEvent(EVENT_NAME_LOGIN_METHOD_START, null, bundle);}private void logAuthorizationMethodComplete(String method, Result result, Map<String, String> loggingExtras) {logAuthorizationMethodComplete(method, result.code.getLoggingValue(), result.errorMessage, result.errorCode,loggingExtras);}private void logAuthorizationMethodComplete(String method, String result, String errorMessage, String errorCode,Map<String, String> loggingExtras) {Bundle bundle = null;if (pendingRequest == null) {// We don't expect this to happen, but if it does, log an event for diagnostic purposes.bundle = newAuthorizationLoggingBundle("");bundle.putString(EVENT_PARAM_LOGIN_RESULT, Result.Code.ERROR.getLoggingValue());bundle.putString(EVENT_PARAM_ERROR_MESSAGE,"Unexpected call to logAuthorizationMethodComplete with null pendingRequest.");} else {bundle = newAuthorizationLoggingBundle(pendingRequest.getAuthId());if (result != null) {bundle.putString(EVENT_PARAM_LOGIN_RESULT, result);}if (errorMessage != null) {bundle.putString(EVENT_PARAM_ERROR_MESSAGE, errorMessage);}if (errorCode != null) {bundle.putString(EVENT_PARAM_ERROR_CODE, errorCode);}if (loggingExtras != null && !loggingExtras.isEmpty()) {JSONObject jsonObject = new JSONObject(loggingExtras);bundle.putString(EVENT_PARAM_EXTRAS, jsonObject.toString());}}bundle.putString(EVENT_PARAM_METHOD, method);bundle.putLong(EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());getAppEventsLogger().logSdkEvent(EVENT_NAME_LOGIN_METHOD_COMPLETE, null, bundle);}static Bundle newAuthorizationLoggingBundle(String authLoggerId) {// We want to log all parameters for all events, to ensure stability of columns across different event types.Bundle bundle = new Bundle();bundle.putLong(EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());bundle.putString(EVENT_PARAM_AUTH_LOGGER_ID, authLoggerId);bundle.putString(EVENT_PARAM_METHOD, "");bundle.putString(EVENT_PARAM_LOGIN_RESULT, "");bundle.putString(EVENT_PARAM_ERROR_MESSAGE, "");bundle.putString(EVENT_PARAM_ERROR_CODE, "");bundle.putString(EVENT_PARAM_EXTRAS, "");return bundle;}abstract class AuthHandler implements Serializable {private static final long serialVersionUID = 1L;Map<String, String> methodLoggingExtras;abstract boolean tryAuthorize(AuthorizationRequest request);abstract String getNameForLogging();boolean onActivityResult(int requestCode, int resultCode, Intent data) {return false;}boolean needsRestart() {return false;}boolean needsInternetPermission() {return false;}void cancel() {}protected void addLoggingExtra(String key, Object value) {if (methodLoggingExtras == null) {methodLoggingExtras = new HashMap<String, String>();}methodLoggingExtras.put(key, value == null ? null : value.toString());}}class WebViewAuthHandler extends AuthHandler {private static final long serialVersionUID = 1L;private transient WebDialog loginDialog;private String applicationId;private String e2e;@OverrideString getNameForLogging() {return "web_view";}@Overrideboolean needsRestart() {// Because we are presenting WebView UI within the current context, we need to explicitly// restart the process if the context goes away and is recreated.return true;}@Overrideboolean needsInternetPermission() {return true;}@Overridevoid cancel() {if (loginDialog != null) {// Since we are calling dismiss explicitly, we need to remove the completion listener to prevent// responding to the upcoming "Cancel" result.loginDialog.setOnCompleteListener(null);loginDialog.dismiss();loginDialog = null;}}@Overrideboolean tryAuthorize(final AuthorizationRequest request) {applicationId = request.getApplicationId();Bundle parameters = new Bundle();if (!Utility.isNullOrEmpty(request.getPermissions())) {String scope = TextUtils.join(",", request.getPermissions());parameters.putString(ServerProtocol.DIALOG_PARAM_SCOPE, scope);addLoggingExtra(ServerProtocol.DIALOG_PARAM_SCOPE, scope);}SessionDefaultAudience audience = request.getDefaultAudience();parameters.putString(ServerProtocol.DIALOG_PARAM_DEFAULT_AUDIENCE, audience.getNativeProtocolAudience());String previousToken = request.getPreviousAccessToken();if (!Utility.isNullOrEmpty(previousToken) && (previousToken.equals(loadCookieToken()))) {parameters.putString(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, previousToken);// Don't log the actual access token, just its presence or absence.addLoggingExtra(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, AppEventsConstants.EVENT_PARAM_VALUE_YES);} else {// The call to clear cookies will create the first instance of CookieSyncManager if necessaryUtility.clearFacebookCookies(context);addLoggingExtra(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, AppEventsConstants.EVENT_PARAM_VALUE_NO);}WebDialog.OnCompleteListener listener = new WebDialog.OnCompleteListener() {@Overridepublic void onComplete(Bundle values, FacebookException error) {onWebDialogComplete(request, values, error);}};e2e = getE2E();addLoggingExtra(ServerProtocol.DIALOG_PARAM_E2E, e2e);WebDialog.Builder builder =new AuthDialogBuilder(getStartActivityDelegate().getActivityContext(), applicationId, parameters).setE2E(e2e).setIsRerequest(request.isRerequest()).setOnCompleteListener(listener);loginDialog = builder.build();loginDialog.show();return true;}void onWebDialogComplete(AuthorizationRequest request, Bundle values,FacebookException error) {Result outcome;if (values != null) {// Actual e2e we got from the dialog should be used for logging.if (values.containsKey(ServerProtocol.DIALOG_PARAM_E2E)) {e2e = values.getString(ServerProtocol.DIALOG_PARAM_E2E);}AccessToken token = AccessToken.createFromWebBundle(request.getPermissions(), values, AccessTokenSource.WEB_VIEW);outcome = Result.createTokenResult(pendingRequest, token);// Ensure any cookies set by the dialog are saved// This is to work around a bug where CookieManager may fail to instantiate if CookieSyncManager// has never been created.CookieSyncManager syncManager = CookieSyncManager.createInstance(context);syncManager.sync();saveCookieToken(token.getToken());} else {if (error instanceof FacebookOperationCanceledException) {outcome = Result.createCancelResult(pendingRequest, "User canceled log in.");} else {// Something went wrong, don't log a completion event since it will skew timing results.e2e = null;String errorCode = null;String errorMessage = error.getMessage();if (error instanceof FacebookServiceException) {FacebookRequestError requestError = ((FacebookServiceException)error).getRequestError();errorCode = String.format("%d", requestError.getErrorCode());errorMessage = requestError.toString();}outcome = Result.createErrorResult(pendingRequest, null, errorMessage, errorCode);}}if (!Utility.isNullOrEmpty(e2e)) {logWebLoginCompleted(applicationId, e2e);}completeAndValidate(outcome);}private void saveCookieToken(String token) {Context context = getStartActivityDelegate().getActivityContext();context.getSharedPreferences(WEB_VIEW_AUTH_HANDLER_STORE,Context.MODE_PRIVATE).edit().putString(WEB_VIEW_AUTH_HANDLER_TOKEN_KEY, token).apply();}private String loadCookieToken() {Context context = getStartActivityDelegate().getActivityContext();SharedPreferences sharedPreferences = context.getSharedPreferences(WEB_VIEW_AUTH_HANDLER_STORE,Context.MODE_PRIVATE);return sharedPreferences.getString(WEB_VIEW_AUTH_HANDLER_TOKEN_KEY, "");}}class GetTokenAuthHandler extends AuthHandler {private static final long serialVersionUID = 1L;private transient GetTokenClient getTokenClient;@OverrideString getNameForLogging() {return "get_token";}@Overridevoid cancel() {if (getTokenClient != null) {getTokenClient.cancel();getTokenClient = null;}}@Overrideboolean needsRestart() {// if the getTokenClient is null, that means an orientation change has occurred, and we need// to recreate the GetTokenClient, so return true to indicate we need a restartreturn getTokenClient == null;}boolean tryAuthorize(final AuthorizationRequest request) {getTokenClient = new GetTokenClient(context, request.getApplicationId());if (!getTokenClient.start()) {return false;}notifyBackgroundProcessingStart();GetTokenClient.CompletedListener callback = new GetTokenClient.CompletedListener() {@Overridepublic void completed(Bundle result) {getTokenCompleted(request, result);}};getTokenClient.setCompletedListener(callback);return true;}void getTokenCompleted(AuthorizationRequest request, Bundle result) {getTokenClient = null;notifyBackgroundProcessingStop();if (result != null) {ArrayList<String> currentPermissions = result.getStringArrayList(NativeProtocol.EXTRA_PERMISSIONS);List<String> permissions = request.getPermissions();if ((currentPermissions != null) &&((permissions == null) || currentPermissions.containsAll(permissions))) {// We got all the permissions we needed, so we can complete the auth now.AccessToken token = AccessToken.createFromNativeLogin(result, AccessTokenSource.FACEBOOK_APPLICATION_SERVICE);Result outcome = Result.createTokenResult(pendingRequest, token);completeAndValidate(outcome);return;}// We didn't get all the permissions we wanted, so update the request with just the permissions// we still need.List<String> newPermissions = new ArrayList<String>();for (String permission : permissions) {if (!currentPermissions.contains(permission)) {newPermissions.add(permission);}}if (!newPermissions.isEmpty()) {addLoggingExtra(EVENT_EXTRAS_NEW_PERMISSIONS, TextUtils.join(",", newPermissions));}request.setPermissions(newPermissions);}tryNextHandler();}}abstract class KatanaAuthHandler extends AuthHandler {private static final long serialVersionUID = 1L;protected boolean tryIntent(Intent intent, int requestCode) {if (intent == null) {return false;}try {getStartActivityDelegate().startActivityForResult(intent, requestCode);} catch (ActivityNotFoundException e) {// We don't expect this to happen, since we've already validated the intent and bailed out before// now if it couldn't be resolved.return false;}return true;}}class KatanaProxyAuthHandler extends KatanaAuthHandler {private static final long serialVersionUID = 1L;private String applicationId;@OverrideString getNameForLogging() {return "katana_proxy_auth";}@Overrideboolean tryAuthorize(AuthorizationRequest request) {applicationId = request.getApplicationId();String e2e = getE2E();Intent intent = NativeProtocol.createProxyAuthIntent(context, request.getApplicationId(),request.getPermissions(), e2e, request.isRerequest(), request.getDefaultAudience());addLoggingExtra(ServerProtocol.DIALOG_PARAM_E2E, e2e);return tryIntent(intent, request.getRequestCode());}@Overrideboolean onActivityResult(int requestCode, int resultCode, Intent data) {// Handle stuffResult outcome;if (data == null) {// This happens if the user presses 'Back'.outcome = Result.createCancelResult(pendingRequest, "Operation canceled");} else if (resultCode == Activity.RESULT_CANCELED) {outcome = Result.createCancelResult(pendingRequest, data.getStringExtra("error"));} else if (resultCode != Activity.RESULT_OK) {outcome = Result.createErrorResult(pendingRequest, "Unexpected resultCode from authorization.", null);} else {outcome = handleResultOk(data);}if (outcome != null) {completeAndValidate(outcome);} else {tryNextHandler();}return true;}private Result handleResultOk(Intent data) {Bundle extras = data.getExtras();String error = extras.getString("error");if (error == null) {error = extras.getString("error_type");}String errorCode = extras.getString("error_code");String errorMessage = extras.getString("error_message");if (errorMessage == null) {errorMessage = extras.getString("error_description");}String e2e = extras.getString(NativeProtocol.FACEBOOK_PROXY_AUTH_E2E_KEY);if (!Utility.isNullOrEmpty(e2e)) {logWebLoginCompleted(applicationId, e2e);}if (error == null && errorCode == null && errorMessage == null) {AccessToken token = AccessToken.createFromWebBundle(pendingRequest.getPermissions(), extras,AccessTokenSource.FACEBOOK_APPLICATION_WEB);return Result.createTokenResult(pendingRequest, token);} else if (ServerProtocol.errorsProxyAuthDisabled.contains(error)) {return null;} else if (ServerProtocol.errorsUserCanceled.contains(error)) {return Result.createCancelResult(pendingRequest, null);} else {return Result.createErrorResult(pendingRequest, error, errorMessage, errorCode);}}}private static String getE2E() {JSONObject e2e = new JSONObject();try {e2e.put("init", System.currentTimeMillis());} catch (JSONException e) {}return e2e.toString();}private void logWebLoginCompleted(String applicationId, String e2e) {AppEventsLogger appEventsLogger = AppEventsLogger.newLogger(context, applicationId);Bundle parameters = new Bundle();parameters.putString(AnalyticsEvents.PARAMETER_WEB_LOGIN_E2E, e2e);parameters.putLong(AnalyticsEvents.PARAMETER_WEB_LOGIN_SWITCHBACK_TIME, System.currentTimeMillis());parameters.putString(AnalyticsEvents.PARAMETER_APP_ID, applicationId);appEventsLogger.logSdkEvent(AnalyticsEvents.EVENT_WEB_LOGIN_COMPLETE, null, parameters);}static class AuthDialogBuilder extends WebDialog.Builder {private static final String OAUTH_DIALOG = "oauth";static final String REDIRECT_URI = "fbconnect://success";private String e2e;private boolean isRerequest;public AuthDialogBuilder(Context context, String applicationId, Bundle parameters) {super(context, applicationId, OAUTH_DIALOG, parameters);}public AuthDialogBuilder setE2E(String e2e) {this.e2e = e2e;return this;}public AuthDialogBuilder setIsRerequest(boolean isRerequest) {this.isRerequest = isRerequest;return this;}@Overridepublic WebDialog build() {Bundle parameters = getParameters();parameters.putString(ServerProtocol.DIALOG_PARAM_REDIRECT_URI, REDIRECT_URI);parameters.putString(ServerProtocol.DIALOG_PARAM_CLIENT_ID, getApplicationId());parameters.putString(ServerProtocol.DIALOG_PARAM_E2E, e2e);parameters.putString(ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE, ServerProtocol.DIALOG_RESPONSE_TYPE_TOKEN);parameters.putString(ServerProtocol.DIALOG_PARAM_RETURN_SCOPES, ServerProtocol.DIALOG_RETURN_SCOPES_TRUE);// Only set the rerequest auth type for non legacy requestsif (isRerequest && !Settings.getPlatformCompatibilityEnabled()) {parameters.putString(ServerProtocol.DIALOG_PARAM_AUTH_TYPE, ServerProtocol.DIALOG_REREQUEST_AUTH_TYPE);}return new WebDialog(getContext(), OAUTH_DIALOG, parameters, getTheme(), getListener());}}static class AuthorizationRequest implements Serializable {private static final long serialVersionUID = 1L;private transient final StartActivityDelegate startActivityDelegate;private final SessionLoginBehavior loginBehavior;private final int requestCode;private boolean isLegacy = false;private List<String> permissions;private final SessionDefaultAudience defaultAudience;private final String applicationId;private final String previousAccessToken;private final String authId;private boolean isRerequest = false;AuthorizationRequest(SessionLoginBehavior loginBehavior, int requestCode, boolean isLegacy,List<String> permissions, SessionDefaultAudience defaultAudience, String applicationId,String validateSameFbidAsToken, StartActivityDelegate startActivityDelegate, String authId) {this.loginBehavior = loginBehavior;this.requestCode = requestCode;this.isLegacy = isLegacy;this.permissions = permissions;this.defaultAudience = defaultAudience;this.applicationId = applicationId;this.previousAccessToken = validateSameFbidAsToken;this.startActivityDelegate = startActivityDelegate;this.authId = authId;}StartActivityDelegate getStartActivityDelegate() {return startActivityDelegate;}List<String> getPermissions() {return permissions;}void setPermissions(List<String> permissions) {this.permissions = permissions;}SessionLoginBehavior getLoginBehavior() {return loginBehavior;}int getRequestCode() {return requestCode;}SessionDefaultAudience getDefaultAudience() {return defaultAudience;}String getApplicationId() {return applicationId;}boolean isLegacy() {return isLegacy;}void setIsLegacy(boolean isLegacy) {this.isLegacy = isLegacy;}String getPreviousAccessToken() {return previousAccessToken;}boolean needsNewTokenValidation() {return previousAccessToken != null && !isLegacy;}String getAuthId() {return authId;}boolean isRerequest() {return isRerequest;}void setRerequest(boolean isRerequest) {this.isRerequest = isRerequest;}}static class Result implements Serializable {private static final long serialVersionUID = 1L;enum Code {SUCCESS("success"),CANCEL("cancel"),ERROR("error");private final String loggingValue;Code(String loggingValue) {this.loggingValue = loggingValue;}// For consistency across platforms, we want to use specific string values when logging these results.String getLoggingValue() {return loggingValue;}}final Code code;final AccessToken token;final String errorMessage;final String errorCode;final AuthorizationRequest request;Map<String, String> loggingExtras;private Result(AuthorizationRequest request, Code code, AccessToken token, String errorMessage,String errorCode) {this.request = request;this.token = token;this.errorMessage = errorMessage;this.code = code;this.errorCode = errorCode;}static Result createTokenResult(AuthorizationRequest request, AccessToken token) {return new Result(request, Code.SUCCESS, token, null, null);}static Result createCancelResult(AuthorizationRequest request, String message) {return new Result(request, Code.CANCEL, null, message, null);}static Result createErrorResult(AuthorizationRequest request, String errorType, String errorDescription) {return createErrorResult(request, errorType, errorDescription, null);}static Result createErrorResult(AuthorizationRequest request, String errorType, String errorDescription,String errorCode) {String message = TextUtils.join(": ", Utility.asListNoNulls(errorType, errorDescription));return new Result(request, Code.ERROR, null, message, errorCode);}}}