Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
14792 manas 1
/**
2
 * Copyright 2010-present Facebook.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *    http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.facebook;
18
 
19
import android.Manifest;
20
import android.app.Activity;
21
import android.content.ActivityNotFoundException;
22
import android.content.Context;
23
import android.content.Intent;
24
import android.content.SharedPreferences;
25
import android.content.pm.PackageManager;
26
import android.os.Bundle;
27
import android.text.TextUtils;
28
import android.webkit.CookieSyncManager;
29
import com.facebook.android.R;
30
import com.facebook.internal.AnalyticsEvents;
31
import com.facebook.internal.NativeProtocol;
32
import com.facebook.internal.ServerProtocol;
33
import com.facebook.internal.Utility;
34
import com.facebook.model.GraphUser;
35
import com.facebook.widget.WebDialog;
36
import org.json.JSONException;
37
import org.json.JSONObject;
38
 
39
import java.io.Serializable;
40
import java.util.ArrayList;
41
import java.util.HashMap;
42
import java.util.List;
43
import java.util.Map;
44
 
45
class AuthorizationClient implements Serializable {
46
    private static final long serialVersionUID = 1L;
47
    private static final String TAG = "Facebook-AuthorizationClient";
48
    private static final String WEB_VIEW_AUTH_HANDLER_STORE =
49
            "com.facebook.AuthorizationClient.WebViewAuthHandler.TOKEN_STORE_KEY";
50
    private static final String WEB_VIEW_AUTH_HANDLER_TOKEN_KEY = "TOKEN";
51
 
52
    // Constants for logging login-related data. Some of these are only used by Session, but grouped here for
53
    // maintainability.
54
    private static final String EVENT_NAME_LOGIN_METHOD_START = "fb_mobile_login_method_start";
55
    private static final String EVENT_NAME_LOGIN_METHOD_COMPLETE = "fb_mobile_login_method_complete";
56
    private static final String EVENT_PARAM_METHOD_RESULT_SKIPPED = "skipped";
57
    static final String EVENT_NAME_LOGIN_START = "fb_mobile_login_start";
58
    static final String EVENT_NAME_LOGIN_COMPLETE = "fb_mobile_login_complete";
59
    // Note: to ensure stability of column mappings across the four different event types, we prepend a column
60
    // index to each name, and we log all columns with all events, even if they are empty.
61
    static final String EVENT_PARAM_AUTH_LOGGER_ID = "0_auth_logger_id";
62
    static final String EVENT_PARAM_TIMESTAMP = "1_timestamp_ms";
63
    static final String EVENT_PARAM_LOGIN_RESULT = "2_result";
64
    static final String EVENT_PARAM_METHOD = "3_method";
65
    static final String EVENT_PARAM_ERROR_CODE = "4_error_code";
66
    static final String EVENT_PARAM_ERROR_MESSAGE = "5_error_message";
67
    static final String EVENT_PARAM_EXTRAS = "6_extras";
68
    static final String EVENT_EXTRAS_TRY_LOGIN_ACTIVITY = "try_login_activity";
69
    static final String EVENT_EXTRAS_TRY_LEGACY = "try_legacy";
70
    static final String EVENT_EXTRAS_LOGIN_BEHAVIOR = "login_behavior";
71
    static final String EVENT_EXTRAS_REQUEST_CODE = "request_code";
72
    static final String EVENT_EXTRAS_IS_LEGACY = "is_legacy";
73
    static final String EVENT_EXTRAS_PERMISSIONS = "permissions";
74
    static final String EVENT_EXTRAS_DEFAULT_AUDIENCE = "default_audience";
75
    static final String EVENT_EXTRAS_MISSING_INTERNET_PERMISSION = "no_internet_permission";
76
    static final String EVENT_EXTRAS_NOT_TRIED = "not_tried";
77
    static final String EVENT_EXTRAS_NEW_PERMISSIONS = "new_permissions";
78
 
79
    List<AuthHandler> handlersToTry;
80
    AuthHandler currentHandler;
81
    transient Context context;
82
    transient StartActivityDelegate startActivityDelegate;
83
    transient OnCompletedListener onCompletedListener;
84
    transient BackgroundProcessingListener backgroundProcessingListener;
85
    transient boolean checkedInternetPermission;
86
    AuthorizationRequest pendingRequest;
87
    Map<String, String> loggingExtras;
88
    private transient AppEventsLogger appEventsLogger;
89
 
90
    interface OnCompletedListener {
91
        void onCompleted(Result result);
92
    }
93
 
94
    interface BackgroundProcessingListener {
95
        void onBackgroundProcessingStarted();
96
 
97
        void onBackgroundProcessingStopped();
98
    }
99
 
100
    interface StartActivityDelegate {
101
        public void startActivityForResult(Intent intent, int requestCode);
102
 
103
        public Activity getActivityContext();
104
    }
105
 
106
    void setContext(final Context context) {
107
        this.context = context;
108
        // We rely on individual requests to tell us how to start an activity.
109
        startActivityDelegate = null;
110
    }
111
 
112
    void setContext(final Activity activity) {
113
        this.context = activity;
114
 
115
        // If we are used in the context of an activity, we will always use that activity to
116
        // call startActivityForResult.
117
        startActivityDelegate = new StartActivityDelegate() {
118
            @Override
119
            public void startActivityForResult(Intent intent, int requestCode) {
120
                activity.startActivityForResult(intent, requestCode);
121
            }
122
 
123
            @Override
124
            public Activity getActivityContext() {
125
                return activity;
126
            }
127
        };
128
    }
129
 
130
    void startOrContinueAuth(AuthorizationRequest request) {
131
        if (getInProgress()) {
132
            continueAuth();
133
        } else {
134
            authorize(request);
135
        }
136
    }
137
 
138
    void authorize(AuthorizationRequest request) {
139
        if (request == null) {
140
            return;
141
        }
142
 
143
        if (pendingRequest != null) {
144
            throw new FacebookException("Attempted to authorize while a request is pending.");
145
        }
146
 
147
        if (request.needsNewTokenValidation() && !checkInternetPermission()) {
148
            // We're going to need INTERNET permission later and don't have it, so fail early.
149
            return;
150
        }
151
        pendingRequest = request;
152
        handlersToTry = getHandlerTypes(request);
153
        tryNextHandler();
154
    }
155
 
156
    void continueAuth() {
157
        if (pendingRequest == null || currentHandler == null) {
158
            throw new FacebookException("Attempted to continue authorization without a pending request.");
159
        }
160
 
161
        if (currentHandler.needsRestart()) {
162
            currentHandler.cancel();
163
            tryCurrentHandler();
164
        }
165
    }
166
 
167
    boolean getInProgress() {
168
        return pendingRequest != null && currentHandler != null;
169
    }
170
 
171
    void cancelCurrentHandler() {
172
        if (currentHandler != null) {
173
            currentHandler.cancel();
174
        }
175
    }
176
 
177
    boolean onActivityResult(int requestCode, int resultCode, Intent data) {
178
        if (pendingRequest != null && requestCode == pendingRequest.getRequestCode()) {
179
            return currentHandler.onActivityResult(requestCode, resultCode, data);
180
        }
181
        return false;
182
    }
183
 
184
    private List<AuthHandler> getHandlerTypes(AuthorizationRequest request) {
185
        ArrayList<AuthHandler> handlers = new ArrayList<AuthHandler>();
186
 
187
        final SessionLoginBehavior behavior = request.getLoginBehavior();
188
        if (behavior.allowsKatanaAuth()) {
189
            if (!request.isLegacy()) {
190
                handlers.add(new GetTokenAuthHandler());
191
            }
192
            handlers.add(new KatanaProxyAuthHandler());
193
        }
194
 
195
        if (behavior.allowsWebViewAuth()) {
196
            handlers.add(new WebViewAuthHandler());
197
        }
198
 
199
        return handlers;
200
    }
201
 
202
    boolean checkInternetPermission() {
203
        if (checkedInternetPermission) {
204
            return true;
205
        }
206
 
207
        int permissionCheck = checkPermission(Manifest.permission.INTERNET);
208
        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
209
            String errorType = context.getString(R.string.com_facebook_internet_permission_error_title);
210
            String errorDescription = context.getString(R.string.com_facebook_internet_permission_error_message);
211
            complete(Result.createErrorResult(pendingRequest, errorType, errorDescription));
212
 
213
            return false;
214
        }
215
 
216
        checkedInternetPermission = true;
217
        return true;
218
    }
219
 
220
    void tryNextHandler() {
221
        if (currentHandler != null) {
222
            logAuthorizationMethodComplete(currentHandler.getNameForLogging(), EVENT_PARAM_METHOD_RESULT_SKIPPED,
223
                    null, null, currentHandler.methodLoggingExtras);
224
        }
225
 
226
        while (handlersToTry != null && !handlersToTry.isEmpty()) {
227
            currentHandler = handlersToTry.remove(0);
228
 
229
            boolean started = tryCurrentHandler();
230
 
231
            if (started) {
232
                return;
233
            }
234
        }
235
 
236
        if (pendingRequest != null) {
237
            // We went through all handlers without successfully attempting an auth.
238
            completeWithFailure();
239
        }
240
    }
241
 
242
    private void completeWithFailure() {
243
        complete(Result.createErrorResult(pendingRequest, "Login attempt failed.", null));
244
    }
245
 
246
    private void addLoggingExtra(String key, String value, boolean accumulate) {
247
        if (loggingExtras == null) {
248
            loggingExtras = new HashMap<String, String>();
249
        }
250
        if (loggingExtras.containsKey(key) && accumulate) {
251
            value = loggingExtras.get(key) + "," + value;
252
        }
253
        loggingExtras.put(key, value);
254
    }
255
 
256
    boolean tryCurrentHandler() {
257
        if (currentHandler.needsInternetPermission() && !checkInternetPermission()) {
258
            addLoggingExtra(EVENT_EXTRAS_MISSING_INTERNET_PERMISSION, AppEventsConstants.EVENT_PARAM_VALUE_YES,
259
                    false);
260
            return false;
261
        }
262
 
263
        boolean tried = currentHandler.tryAuthorize(pendingRequest);
264
        if (tried) {
265
            logAuthorizationMethodStart(currentHandler.getNameForLogging());
266
        } else {
267
            // We didn't try it, so we don't get any other completion notification -- log that we skipped it.
268
            addLoggingExtra(EVENT_EXTRAS_NOT_TRIED, currentHandler.getNameForLogging(), true);
269
        }
270
 
271
        return tried;
272
    }
273
 
274
    void completeAndValidate(Result outcome) {
275
        // Do we need to validate a successful result (as in the case of a reauth)?
276
        if (outcome.token != null && pendingRequest.needsNewTokenValidation()) {
277
            validateSameFbidAndFinish(outcome);
278
        } else {
279
            // We're done, just notify the listener.
280
            complete(outcome);
281
        }
282
    }
283
 
284
    void complete(Result outcome) {
285
        // This might be null if, for some reason, none of the handlers were successfully tried (in which case
286
        // we already logged that).
287
        if (currentHandler != null) {
288
            logAuthorizationMethodComplete(currentHandler.getNameForLogging(), outcome,
289
                    currentHandler.methodLoggingExtras);
290
        }
291
 
292
        if (loggingExtras != null) {
293
            // Pass this back to the caller for logging at the aggregate level.
294
            outcome.loggingExtras = loggingExtras;
295
        }
296
 
297
        handlersToTry = null;
298
        currentHandler = null;
299
        pendingRequest = null;
300
        loggingExtras = null;
301
 
302
        notifyOnCompleteListener(outcome);
303
    }
304
 
305
    OnCompletedListener getOnCompletedListener() {
306
        return onCompletedListener;
307
    }
308
 
309
    void setOnCompletedListener(OnCompletedListener onCompletedListener) {
310
        this.onCompletedListener = onCompletedListener;
311
    }
312
 
313
    BackgroundProcessingListener getBackgroundProcessingListener() {
314
        return backgroundProcessingListener;
315
    }
316
 
317
    void setBackgroundProcessingListener(BackgroundProcessingListener backgroundProcessingListener) {
318
        this.backgroundProcessingListener = backgroundProcessingListener;
319
    }
320
 
321
    StartActivityDelegate getStartActivityDelegate() {
322
        if (startActivityDelegate != null) {
323
            return startActivityDelegate;
324
        } else if (pendingRequest != null) {
325
            // Wrap the request's delegate in our own.
326
            return new StartActivityDelegate() {
327
                @Override
328
                public void startActivityForResult(Intent intent, int requestCode) {
329
                    pendingRequest.getStartActivityDelegate().startActivityForResult(intent, requestCode);
330
                }
331
 
332
                @Override
333
                public Activity getActivityContext() {
334
                    return pendingRequest.getStartActivityDelegate().getActivityContext();
335
                }
336
            };
337
        }
338
        return null;
339
    }
340
 
341
    int checkPermission(String permission) {
342
        return context.checkCallingOrSelfPermission(permission);
343
    }
344
 
345
    void validateSameFbidAndFinish(Result pendingResult) {
346
        if (pendingResult.token == null) {
347
            throw new FacebookException("Can't validate without a token");
348
        }
349
 
350
        RequestBatch batch = createReauthValidationBatch(pendingResult);
351
 
352
        notifyBackgroundProcessingStart();
353
 
354
        batch.executeAsync();
355
    }
356
 
357
    RequestBatch createReauthValidationBatch(final Result pendingResult) {
358
        // We need to ensure that the token we got represents the same fbid as the old one. We issue
359
        // a "me" request using the current token, a "me" request using the new token, and a "me/permissions"
360
        // request using the current token to get the permissions of the user.
361
 
362
        final ArrayList<String> fbids = new ArrayList<String>();
363
        final ArrayList<String> grantedPermissions = new ArrayList<String>();
364
        final ArrayList<String> declinedPermissions = new ArrayList<String>();
365
        final String newToken = pendingResult.token.getToken();
366
 
367
        Request.Callback meCallback = new Request.Callback() {
368
            @Override
369
            public void onCompleted(Response response) {
370
                try {
371
                    GraphUser user = response.getGraphObjectAs(GraphUser.class);
372
                    if (user != null) {
373
                        fbids.add(user.getId());
374
                    }
375
                } catch (Exception ex) {
376
                }
377
            }
378
        };
379
 
380
        String validateSameFbidAsToken = pendingRequest.getPreviousAccessToken();
381
        Request requestCurrentTokenMe = createGetProfileIdRequest(validateSameFbidAsToken);
382
        requestCurrentTokenMe.setCallback(meCallback);
383
 
384
        Request requestNewTokenMe = createGetProfileIdRequest(newToken);
385
        requestNewTokenMe.setCallback(meCallback);
386
 
387
        Request requestCurrentTokenPermissions = createGetPermissionsRequest(validateSameFbidAsToken);
388
        requestCurrentTokenPermissions.setCallback(new Request.Callback() {
389
            @Override
390
            public void onCompleted(Response response) {
391
                try {
392
                    Session.PermissionsPair permissionsPair = Session.handlePermissionResponse(response);
393
                    if (permissionsPair != null) {
394
                        grantedPermissions.addAll(permissionsPair.getGrantedPermissions());
395
                        declinedPermissions.addAll(permissionsPair.getDeclinedPermissions());
396
                    }
397
                } catch (Exception ex) {
398
                }
399
            }
400
        });
401
 
402
        RequestBatch batch = new RequestBatch(requestCurrentTokenMe, requestNewTokenMe,
403
                requestCurrentTokenPermissions);
404
        batch.setBatchApplicationId(pendingRequest.getApplicationId());
405
        batch.addCallback(new RequestBatch.Callback() {
406
            @Override
407
            public void onBatchCompleted(RequestBatch batch) {
408
                try {
409
                    Result result = null;
410
                    if (fbids.size() == 2 && fbids.get(0) != null && fbids.get(1) != null &&
411
                            fbids.get(0).equals(fbids.get(1))) {
412
                        // Modify the token to have the right permission set.
413
                        AccessToken tokenWithPermissions = AccessToken
414
                                .createFromTokenWithRefreshedPermissions(pendingResult.token,
415
                                        grantedPermissions, declinedPermissions);
416
                        result = Result.createTokenResult(pendingRequest, tokenWithPermissions);
417
                    } else {
418
                        result = Result
419
                                .createErrorResult(pendingRequest, "User logged in as different Facebook user.", null);
420
                    }
421
                    complete(result);
422
                } catch (Exception ex) {
423
                    complete(Result.createErrorResult(pendingRequest, "Caught exception", ex.getMessage()));
424
                } finally {
425
                    notifyBackgroundProcessingStop();
426
                }
427
            }
428
        });
429
 
430
        return batch;
431
    }
432
 
433
    Request createGetPermissionsRequest(String accessToken) {
434
        Bundle parameters = new Bundle();
435
        parameters.putString("access_token", accessToken);
436
        return new Request(null, "me/permissions", parameters, HttpMethod.GET, null);
437
    }
438
 
439
    Request createGetProfileIdRequest(String accessToken) {
440
        Bundle parameters = new Bundle();
441
        parameters.putString("fields", "id");
442
        parameters.putString("access_token", accessToken);
443
        return new Request(null, "me", parameters, HttpMethod.GET, null);
444
    }
445
 
446
    private AppEventsLogger getAppEventsLogger() {
447
        if (appEventsLogger == null || !appEventsLogger.getApplicationId().equals(pendingRequest.getApplicationId())) {
448
            appEventsLogger = AppEventsLogger.newLogger(context, pendingRequest.getApplicationId());
449
        }
450
        return appEventsLogger;
451
    }
452
 
453
    private void notifyOnCompleteListener(Result outcome) {
454
        if (onCompletedListener != null) {
455
            onCompletedListener.onCompleted(outcome);
456
        }
457
    }
458
 
459
    private void notifyBackgroundProcessingStart() {
460
        if (backgroundProcessingListener != null) {
461
            backgroundProcessingListener.onBackgroundProcessingStarted();
462
        }
463
    }
464
 
465
    private void notifyBackgroundProcessingStop() {
466
        if (backgroundProcessingListener != null) {
467
            backgroundProcessingListener.onBackgroundProcessingStopped();
468
        }
469
    }
470
 
471
    private void logAuthorizationMethodStart(String method) {
472
        Bundle bundle = newAuthorizationLoggingBundle(pendingRequest.getAuthId());
473
        bundle.putLong(EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());
474
        bundle.putString(EVENT_PARAM_METHOD, method);
475
 
476
        getAppEventsLogger().logSdkEvent(EVENT_NAME_LOGIN_METHOD_START, null, bundle);
477
    }
478
 
479
    private void logAuthorizationMethodComplete(String method, Result result, Map<String, String> loggingExtras) {
480
        logAuthorizationMethodComplete(method, result.code.getLoggingValue(), result.errorMessage, result.errorCode,
481
                loggingExtras);
482
    }
483
 
484
    private void logAuthorizationMethodComplete(String method, String result, String errorMessage, String errorCode,
485
            Map<String, String> loggingExtras) {
486
        Bundle bundle = null;
487
        if (pendingRequest == null) {
488
            // We don't expect this to happen, but if it does, log an event for diagnostic purposes.
489
            bundle = newAuthorizationLoggingBundle("");
490
            bundle.putString(EVENT_PARAM_LOGIN_RESULT, Result.Code.ERROR.getLoggingValue());
491
            bundle.putString(EVENT_PARAM_ERROR_MESSAGE,
492
                    "Unexpected call to logAuthorizationMethodComplete with null pendingRequest.");
493
        } else {
494
            bundle = newAuthorizationLoggingBundle(pendingRequest.getAuthId());
495
            if (result != null) {
496
                bundle.putString(EVENT_PARAM_LOGIN_RESULT, result);
497
            }
498
            if (errorMessage != null) {
499
                bundle.putString(EVENT_PARAM_ERROR_MESSAGE, errorMessage);
500
            }
501
            if (errorCode != null) {
502
                bundle.putString(EVENT_PARAM_ERROR_CODE, errorCode);
503
            }
504
            if (loggingExtras != null && !loggingExtras.isEmpty()) {
505
                JSONObject jsonObject = new JSONObject(loggingExtras);
506
                bundle.putString(EVENT_PARAM_EXTRAS, jsonObject.toString());
507
            }
508
        }
509
        bundle.putString(EVENT_PARAM_METHOD, method);
510
        bundle.putLong(EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());
511
 
512
        getAppEventsLogger().logSdkEvent(EVENT_NAME_LOGIN_METHOD_COMPLETE, null, bundle);
513
    }
514
 
515
    static Bundle newAuthorizationLoggingBundle(String authLoggerId) {
516
        // We want to log all parameters for all events, to ensure stability of columns across different event types.
517
        Bundle bundle = new Bundle();
518
        bundle.putLong(EVENT_PARAM_TIMESTAMP, System.currentTimeMillis());
519
        bundle.putString(EVENT_PARAM_AUTH_LOGGER_ID, authLoggerId);
520
        bundle.putString(EVENT_PARAM_METHOD, "");
521
        bundle.putString(EVENT_PARAM_LOGIN_RESULT, "");
522
        bundle.putString(EVENT_PARAM_ERROR_MESSAGE, "");
523
        bundle.putString(EVENT_PARAM_ERROR_CODE, "");
524
        bundle.putString(EVENT_PARAM_EXTRAS, "");
525
        return bundle;
526
    }
527
 
528
    abstract class AuthHandler implements Serializable {
529
        private static final long serialVersionUID = 1L;
530
 
531
        Map<String, String> methodLoggingExtras;
532
 
533
        abstract boolean tryAuthorize(AuthorizationRequest request);
534
        abstract String getNameForLogging();
535
 
536
        boolean onActivityResult(int requestCode, int resultCode, Intent data) {
537
            return false;
538
        }
539
 
540
        boolean needsRestart() {
541
            return false;
542
        }
543
 
544
        boolean needsInternetPermission() {
545
            return false;
546
        }
547
 
548
        void cancel() {
549
        }
550
 
551
        protected void addLoggingExtra(String key, Object value) {
552
            if (methodLoggingExtras == null) {
553
                methodLoggingExtras = new HashMap<String, String>();
554
            }
555
            methodLoggingExtras.put(key, value == null ? null : value.toString());
556
        }
557
    }
558
 
559
    class WebViewAuthHandler extends AuthHandler {
560
        private static final long serialVersionUID = 1L;
561
        private transient WebDialog loginDialog;
562
        private String applicationId;
563
        private String e2e;
564
 
565
        @Override
566
        String getNameForLogging() {
567
            return "web_view";
568
        }
569
 
570
        @Override
571
        boolean needsRestart() {
572
            // Because we are presenting WebView UI within the current context, we need to explicitly
573
            // restart the process if the context goes away and is recreated.
574
            return true;
575
        }
576
 
577
        @Override
578
        boolean needsInternetPermission() {
579
            return true;
580
        }
581
 
582
        @Override
583
        void cancel() {
584
            if (loginDialog != null) {
585
                // Since we are calling dismiss explicitly, we need to remove the completion listener to prevent
586
                // responding to the upcoming "Cancel" result.
587
                loginDialog.setOnCompleteListener(null);
588
                loginDialog.dismiss();
589
                loginDialog = null;
590
            }
591
        }
592
 
593
        @Override
594
        boolean tryAuthorize(final AuthorizationRequest request) {
595
            applicationId = request.getApplicationId();
596
            Bundle parameters = new Bundle();
597
            if (!Utility.isNullOrEmpty(request.getPermissions())) {
598
                String scope = TextUtils.join(",", request.getPermissions());
599
                parameters.putString(ServerProtocol.DIALOG_PARAM_SCOPE, scope);
600
                addLoggingExtra(ServerProtocol.DIALOG_PARAM_SCOPE, scope);
601
            }
602
 
603
            SessionDefaultAudience audience = request.getDefaultAudience();
604
            parameters.putString(ServerProtocol.DIALOG_PARAM_DEFAULT_AUDIENCE, audience.getNativeProtocolAudience());
605
 
606
            String previousToken = request.getPreviousAccessToken();
607
            if (!Utility.isNullOrEmpty(previousToken) && (previousToken.equals(loadCookieToken()))) {
608
                parameters.putString(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, previousToken);
609
                // Don't log the actual access token, just its presence or absence.
610
                addLoggingExtra(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, AppEventsConstants.EVENT_PARAM_VALUE_YES);
611
            } else {
612
                // The call to clear cookies will create the first instance of CookieSyncManager if necessary
613
                Utility.clearFacebookCookies(context);
614
                addLoggingExtra(ServerProtocol.DIALOG_PARAM_ACCESS_TOKEN, AppEventsConstants.EVENT_PARAM_VALUE_NO);
615
            }
616
 
617
            WebDialog.OnCompleteListener listener = new WebDialog.OnCompleteListener() {
618
                @Override
619
                public void onComplete(Bundle values, FacebookException error) {
620
                    onWebDialogComplete(request, values, error);
621
                }
622
            };
623
 
624
            e2e = getE2E();
625
            addLoggingExtra(ServerProtocol.DIALOG_PARAM_E2E, e2e);
626
 
627
            WebDialog.Builder builder =
628
                    new AuthDialogBuilder(getStartActivityDelegate().getActivityContext(), applicationId, parameters)
629
                            .setE2E(e2e)
630
                            .setIsRerequest(request.isRerequest())
631
                            .setOnCompleteListener(listener);
632
            loginDialog = builder.build();
633
            loginDialog.show();
634
 
635
            return true;
636
        }
637
 
638
        void onWebDialogComplete(AuthorizationRequest request, Bundle values,
639
                FacebookException error) {
640
            Result outcome;
641
            if (values != null) {
642
                // Actual e2e we got from the dialog should be used for logging.
643
                if (values.containsKey(ServerProtocol.DIALOG_PARAM_E2E)) {
644
                    e2e = values.getString(ServerProtocol.DIALOG_PARAM_E2E);
645
                }
646
 
647
                AccessToken token = AccessToken
648
                        .createFromWebBundle(request.getPermissions(), values, AccessTokenSource.WEB_VIEW);
649
                outcome = Result.createTokenResult(pendingRequest, token);
650
 
651
                // Ensure any cookies set by the dialog are saved
652
                // This is to work around a bug where CookieManager may fail to instantiate if CookieSyncManager
653
                // has never been created.
654
                CookieSyncManager syncManager = CookieSyncManager.createInstance(context);
655
                syncManager.sync();
656
                saveCookieToken(token.getToken());
657
            } else {
658
                if (error instanceof FacebookOperationCanceledException) {
659
                    outcome = Result.createCancelResult(pendingRequest, "User canceled log in.");
660
                } else {
661
                    // Something went wrong, don't log a completion event since it will skew timing results.
662
                    e2e = null;
663
 
664
                    String errorCode = null;
665
                    String errorMessage = error.getMessage();
666
                    if (error instanceof FacebookServiceException) {
667
                        FacebookRequestError requestError = ((FacebookServiceException)error).getRequestError();
668
                        errorCode = String.format("%d", requestError.getErrorCode());
669
                        errorMessage = requestError.toString();
670
                    }
671
                    outcome = Result.createErrorResult(pendingRequest, null, errorMessage, errorCode);
672
                }
673
            }
674
 
675
            if (!Utility.isNullOrEmpty(e2e)) {
676
                logWebLoginCompleted(applicationId, e2e);
677
            }
678
 
679
            completeAndValidate(outcome);
680
        }
681
 
682
        private void saveCookieToken(String token) {
683
            Context context = getStartActivityDelegate().getActivityContext();
684
            context.getSharedPreferences(
685
                    WEB_VIEW_AUTH_HANDLER_STORE,
686
                    Context.MODE_PRIVATE)
687
                .edit()
688
                .putString(WEB_VIEW_AUTH_HANDLER_TOKEN_KEY, token)
689
                .apply();
690
        }
691
 
692
        private String loadCookieToken() {
693
            Context context = getStartActivityDelegate().getActivityContext();
694
            SharedPreferences sharedPreferences = context.getSharedPreferences(
695
                    WEB_VIEW_AUTH_HANDLER_STORE,
696
                    Context.MODE_PRIVATE);
697
            return sharedPreferences.getString(WEB_VIEW_AUTH_HANDLER_TOKEN_KEY, "");
698
        }
699
    }
700
 
701
    class GetTokenAuthHandler extends AuthHandler {
702
        private static final long serialVersionUID = 1L;
703
        private transient GetTokenClient getTokenClient;
704
 
705
        @Override
706
        String getNameForLogging() {
707
            return "get_token";
708
        }
709
 
710
        @Override
711
        void cancel() {
712
            if (getTokenClient != null) {
713
                getTokenClient.cancel();
714
                getTokenClient = null;
715
            }
716
        }
717
 
718
        @Override
719
        boolean needsRestart() {
720
            // if the getTokenClient is null, that means an orientation change has occurred, and we need
721
            // to recreate the GetTokenClient, so return true to indicate we need a restart
722
            return getTokenClient == null;
723
        }
724
 
725
        boolean tryAuthorize(final AuthorizationRequest request) {
726
            getTokenClient = new GetTokenClient(context, request.getApplicationId());
727
            if (!getTokenClient.start()) {
728
                return false;
729
            }
730
 
731
            notifyBackgroundProcessingStart();
732
 
733
            GetTokenClient.CompletedListener callback = new GetTokenClient.CompletedListener() {
734
                @Override
735
                public void completed(Bundle result) {
736
                    getTokenCompleted(request, result);
737
                }
738
            };
739
 
740
            getTokenClient.setCompletedListener(callback);
741
            return true;
742
        }
743
 
744
        void getTokenCompleted(AuthorizationRequest request, Bundle result) {
745
            getTokenClient = null;
746
 
747
            notifyBackgroundProcessingStop();
748
 
749
            if (result != null) {
750
                ArrayList<String> currentPermissions = result.getStringArrayList(NativeProtocol.EXTRA_PERMISSIONS);
751
                List<String> permissions = request.getPermissions();
752
                if ((currentPermissions != null) &&
753
                        ((permissions == null) || currentPermissions.containsAll(permissions))) {
754
                    // We got all the permissions we needed, so we can complete the auth now.
755
                    AccessToken token = AccessToken
756
                            .createFromNativeLogin(result, AccessTokenSource.FACEBOOK_APPLICATION_SERVICE);
757
                    Result outcome = Result.createTokenResult(pendingRequest, token);
758
                    completeAndValidate(outcome);
759
                    return;
760
                }
761
 
762
                // We didn't get all the permissions we wanted, so update the request with just the permissions
763
                // we still need.
764
                List<String> newPermissions = new ArrayList<String>();
765
                for (String permission : permissions) {
766
                    if (!currentPermissions.contains(permission)) {
767
                        newPermissions.add(permission);
768
                    }
769
                }
770
                if (!newPermissions.isEmpty()) {
771
                    addLoggingExtra(EVENT_EXTRAS_NEW_PERMISSIONS, TextUtils.join(",", newPermissions));
772
                }
773
 
774
                request.setPermissions(newPermissions);
775
            }
776
 
777
            tryNextHandler();
778
        }
779
    }
780
 
781
    abstract class KatanaAuthHandler extends AuthHandler {
782
        private static final long serialVersionUID = 1L;
783
 
784
        protected boolean tryIntent(Intent intent, int requestCode) {
785
            if (intent == null) {
786
                return false;
787
            }
788
 
789
            try {
790
                getStartActivityDelegate().startActivityForResult(intent, requestCode);
791
            } catch (ActivityNotFoundException e) {
792
                // We don't expect this to happen, since we've already validated the intent and bailed out before
793
                // now if it couldn't be resolved.
794
                return false;
795
            }
796
 
797
            return true;
798
        }
799
    }
800
 
801
    class KatanaProxyAuthHandler extends KatanaAuthHandler {
802
        private static final long serialVersionUID = 1L;
803
        private String applicationId;
804
 
805
        @Override
806
        String getNameForLogging() {
807
            return "katana_proxy_auth";
808
        }
809
 
810
        @Override
811
        boolean tryAuthorize(AuthorizationRequest request) {
812
            applicationId = request.getApplicationId();
813
 
814
            String e2e = getE2E();
815
            Intent intent = NativeProtocol.createProxyAuthIntent(context, request.getApplicationId(),
816
                    request.getPermissions(), e2e, request.isRerequest(), request.getDefaultAudience());
817
 
818
            addLoggingExtra(ServerProtocol.DIALOG_PARAM_E2E, e2e);
819
 
820
            return tryIntent(intent, request.getRequestCode());
821
        }
822
 
823
        @Override
824
        boolean onActivityResult(int requestCode, int resultCode, Intent data) {
825
            // Handle stuff
826
            Result outcome;
827
 
828
            if (data == null) {
829
                // This happens if the user presses 'Back'.
830
                outcome = Result.createCancelResult(pendingRequest, "Operation canceled");
831
            } else if (resultCode == Activity.RESULT_CANCELED) {
832
                outcome = Result.createCancelResult(pendingRequest, data.getStringExtra("error"));
833
            } else if (resultCode != Activity.RESULT_OK) {
834
                outcome = Result.createErrorResult(pendingRequest, "Unexpected resultCode from authorization.", null);
835
            } else {
836
                outcome = handleResultOk(data);
837
            }
838
 
839
            if (outcome != null) {
840
                completeAndValidate(outcome);
841
            } else {
842
                tryNextHandler();
843
            }
844
            return true;
845
        }
846
 
847
        private Result handleResultOk(Intent data) {
848
            Bundle extras = data.getExtras();
849
            String error = extras.getString("error");
850
            if (error == null) {
851
                error = extras.getString("error_type");
852
            }
853
            String errorCode = extras.getString("error_code");
854
            String errorMessage = extras.getString("error_message");
855
            if (errorMessage == null) {
856
                errorMessage = extras.getString("error_description");
857
            }
858
 
859
            String e2e = extras.getString(NativeProtocol.FACEBOOK_PROXY_AUTH_E2E_KEY);
860
            if (!Utility.isNullOrEmpty(e2e)) {
861
                logWebLoginCompleted(applicationId, e2e);
862
            }
863
 
864
            if (error == null && errorCode == null && errorMessage == null) {
865
                AccessToken token = AccessToken.createFromWebBundle(pendingRequest.getPermissions(), extras,
866
                        AccessTokenSource.FACEBOOK_APPLICATION_WEB);
867
                return Result.createTokenResult(pendingRequest, token);
868
            } else if (ServerProtocol.errorsProxyAuthDisabled.contains(error)) {
869
                return null;
870
            } else if (ServerProtocol.errorsUserCanceled.contains(error)) {
871
                return Result.createCancelResult(pendingRequest, null);
872
            } else {
873
                return Result.createErrorResult(pendingRequest, error, errorMessage, errorCode);
874
            }
875
        }
876
    }
877
 
878
    private static String getE2E() {
879
        JSONObject e2e = new JSONObject();
880
        try {
881
            e2e.put("init", System.currentTimeMillis());
882
        } catch (JSONException e) {
883
        }
884
        return e2e.toString();
885
    }
886
 
887
    private void logWebLoginCompleted(String applicationId, String e2e) {
888
        AppEventsLogger appEventsLogger = AppEventsLogger.newLogger(context, applicationId);
889
 
890
        Bundle parameters = new Bundle();
891
        parameters.putString(AnalyticsEvents.PARAMETER_WEB_LOGIN_E2E, e2e);
892
        parameters.putLong(AnalyticsEvents.PARAMETER_WEB_LOGIN_SWITCHBACK_TIME, System.currentTimeMillis());
893
        parameters.putString(AnalyticsEvents.PARAMETER_APP_ID, applicationId);
894
 
895
        appEventsLogger.logSdkEvent(AnalyticsEvents.EVENT_WEB_LOGIN_COMPLETE, null, parameters);
896
    }
897
 
898
    static class AuthDialogBuilder extends WebDialog.Builder {
899
        private static final String OAUTH_DIALOG = "oauth";
900
        static final String REDIRECT_URI = "fbconnect://success";
901
        private String e2e;
902
        private boolean isRerequest;
903
 
904
        public AuthDialogBuilder(Context context, String applicationId, Bundle parameters) {
905
            super(context, applicationId, OAUTH_DIALOG, parameters);
906
        }
907
 
908
        public AuthDialogBuilder setE2E(String e2e) {
909
            this.e2e = e2e;
910
            return this;
911
        }
912
 
913
        public AuthDialogBuilder setIsRerequest(boolean isRerequest) {
914
            this.isRerequest = isRerequest;
915
            return this;
916
        }
917
 
918
        @Override
919
        public WebDialog build() {
920
            Bundle parameters = getParameters();
921
            parameters.putString(ServerProtocol.DIALOG_PARAM_REDIRECT_URI, REDIRECT_URI);
922
            parameters.putString(ServerProtocol.DIALOG_PARAM_CLIENT_ID, getApplicationId());
923
            parameters.putString(ServerProtocol.DIALOG_PARAM_E2E, e2e);
924
            parameters.putString(ServerProtocol.DIALOG_PARAM_RESPONSE_TYPE, ServerProtocol.DIALOG_RESPONSE_TYPE_TOKEN);
925
            parameters.putString(ServerProtocol.DIALOG_PARAM_RETURN_SCOPES, ServerProtocol.DIALOG_RETURN_SCOPES_TRUE);
926
 
927
            // Only set the rerequest auth type for non legacy requests
928
            if (isRerequest && !Settings.getPlatformCompatibilityEnabled()) {
929
                parameters.putString(ServerProtocol.DIALOG_PARAM_AUTH_TYPE, ServerProtocol.DIALOG_REREQUEST_AUTH_TYPE);
930
            }
931
 
932
            return new WebDialog(getContext(), OAUTH_DIALOG, parameters, getTheme(), getListener());
933
        }
934
    }
935
 
936
    static class AuthorizationRequest implements Serializable {
937
        private static final long serialVersionUID = 1L;
938
 
939
        private transient final StartActivityDelegate startActivityDelegate;
940
        private final SessionLoginBehavior loginBehavior;
941
        private final int requestCode;
942
        private boolean isLegacy = false;
943
        private List<String> permissions;
944
        private final SessionDefaultAudience defaultAudience;
945
        private final String applicationId;
946
        private final String previousAccessToken;
947
        private final String authId;
948
        private boolean isRerequest = false;
949
 
950
        AuthorizationRequest(SessionLoginBehavior loginBehavior, int requestCode, boolean isLegacy,
951
                List<String> permissions, SessionDefaultAudience defaultAudience, String applicationId,
952
                String validateSameFbidAsToken, StartActivityDelegate startActivityDelegate, String authId) {
953
            this.loginBehavior = loginBehavior;
954
            this.requestCode = requestCode;
955
            this.isLegacy = isLegacy;
956
            this.permissions = permissions;
957
            this.defaultAudience = defaultAudience;
958
            this.applicationId = applicationId;
959
            this.previousAccessToken = validateSameFbidAsToken;
960
            this.startActivityDelegate = startActivityDelegate;
961
            this.authId = authId;
962
        }
963
 
964
        StartActivityDelegate getStartActivityDelegate() {
965
            return startActivityDelegate;
966
        }
967
 
968
        List<String> getPermissions() {
969
            return permissions;
970
        }
971
 
972
        void setPermissions(List<String> permissions) {
973
            this.permissions = permissions;
974
        }
975
 
976
        SessionLoginBehavior getLoginBehavior() {
977
            return loginBehavior;
978
        }
979
 
980
        int getRequestCode() {
981
            return requestCode;
982
        }
983
 
984
        SessionDefaultAudience getDefaultAudience() {
985
            return defaultAudience;
986
        }
987
 
988
        String getApplicationId() {
989
            return applicationId;
990
        }
991
 
992
        boolean isLegacy() {
993
            return isLegacy;
994
        }
995
 
996
        void setIsLegacy(boolean isLegacy) {
997
            this.isLegacy = isLegacy;
998
        }
999
 
1000
        String getPreviousAccessToken() {
1001
            return previousAccessToken;
1002
        }
1003
 
1004
        boolean needsNewTokenValidation() {
1005
            return previousAccessToken != null && !isLegacy;
1006
        }
1007
 
1008
        String getAuthId() {
1009
            return authId;
1010
        }
1011
 
1012
        boolean isRerequest() {
1013
            return isRerequest;
1014
        }
1015
 
1016
        void setRerequest(boolean isRerequest) {
1017
            this.isRerequest = isRerequest;
1018
        }
1019
    }
1020
 
1021
 
1022
    static class Result implements Serializable {
1023
        private static final long serialVersionUID = 1L;
1024
 
1025
        enum Code {
1026
            SUCCESS("success"),
1027
            CANCEL("cancel"),
1028
            ERROR("error");
1029
 
1030
            private final String loggingValue;
1031
 
1032
            Code(String loggingValue) {
1033
                this.loggingValue = loggingValue;
1034
            }
1035
 
1036
            // For consistency across platforms, we want to use specific string values when logging these results.
1037
            String getLoggingValue() {
1038
                return loggingValue;
1039
            }
1040
        }
1041
 
1042
        final Code code;
1043
        final AccessToken token;
1044
        final String errorMessage;
1045
        final String errorCode;
1046
        final AuthorizationRequest request;
1047
        Map<String, String> loggingExtras;
1048
 
1049
        private Result(AuthorizationRequest request, Code code, AccessToken token, String errorMessage,
1050
                String errorCode) {
1051
            this.request = request;
1052
            this.token = token;
1053
            this.errorMessage = errorMessage;
1054
            this.code = code;
1055
            this.errorCode = errorCode;
1056
        }
1057
 
1058
        static Result createTokenResult(AuthorizationRequest request, AccessToken token) {
1059
            return new Result(request, Code.SUCCESS, token, null, null);
1060
        }
1061
 
1062
        static Result createCancelResult(AuthorizationRequest request, String message) {
1063
            return new Result(request, Code.CANCEL, null, message, null);
1064
        }
1065
 
1066
        static Result createErrorResult(AuthorizationRequest request, String errorType, String errorDescription) {
1067
            return createErrorResult(request, errorType, errorDescription, null);
1068
        }
1069
 
1070
        static Result createErrorResult(AuthorizationRequest request, String errorType, String errorDescription,
1071
                String errorCode) {
1072
            String message = TextUtils.join(": ", Utility.asListNoNulls(errorType, errorDescription));
1073
            return new Result(request, Code.ERROR, null, message, errorCode);
1074
        }
1075
    }
1076
}