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.annotation.SuppressLint;
20
import android.content.Intent;
21
import android.os.Bundle;
22
import android.text.TextUtils;
23
import com.facebook.internal.NativeProtocol;
24
import com.facebook.internal.Utility;
25
import com.facebook.internal.Validate;
26
 
27
import java.io.InvalidObjectException;
28
import java.io.ObjectInputStream;
29
import java.io.Serializable;
30
import java.util.*;
31
 
32
/**
33
 * This class represents an access token returned by the Facebook Login service, along with associated
34
 * metadata such as its expiration date and permissions. In general, the {@link Session} class will
35
 * abstract away the need to worry about the details of an access token, but there are situations
36
 * (such as handling native links, importing previously-obtained access tokens, etc.) where it is
37
 * useful to deal with access tokens directly. Factory methods are provided to construct access tokens.
38
 * <p/>
39
 * For more information on access tokens, see
40
 * <a href="https://developers.facebook.com/docs/facebook-login/access-tokens/">Access Tokens</a>.
41
 */
42
public final class AccessToken implements Serializable {
43
    private static final long serialVersionUID = 1L;
44
    static final String ACCESS_TOKEN_KEY = "access_token";
45
    static final String EXPIRES_IN_KEY = "expires_in";
46
    private static final Date MIN_DATE = new Date(Long.MIN_VALUE);
47
    private static final Date MAX_DATE = new Date(Long.MAX_VALUE);
48
    private static final Date DEFAULT_EXPIRATION_TIME = MAX_DATE;
49
    private static final Date DEFAULT_LAST_REFRESH_TIME = new Date();
50
    private static final AccessTokenSource DEFAULT_ACCESS_TOKEN_SOURCE = AccessTokenSource.FACEBOOK_APPLICATION_WEB;
51
    private static final Date ALREADY_EXPIRED_EXPIRATION_TIME = MIN_DATE;
52
 
53
    private final Date expires;
54
    private final List<String> permissions;
55
    private final List<String> declinedPermissions;
56
    private final String token;
57
    private final AccessTokenSource source;
58
    private final Date lastRefresh;
59
 
60
    AccessToken(String token, Date expires, List<String> permissions, List<String> declinedPermissions, AccessTokenSource source, Date lastRefresh) {
61
        if (permissions == null) {
62
            permissions = Collections.emptyList();
63
        }
64
        if (declinedPermissions == null) {
65
            declinedPermissions = Collections.emptyList();
66
        }
67
 
68
        this.expires = expires;
69
        this.permissions = Collections.unmodifiableList(permissions);
70
        this.declinedPermissions = Collections.unmodifiableList(declinedPermissions);
71
        this.token = token;
72
        this.source = source;
73
        this.lastRefresh = lastRefresh;
74
    }
75
 
76
    /**
77
     * Gets the string representing the access token.
78
     *
79
     * @return the string representing the access token
80
     */
81
    public String getToken() {
82
        return this.token;
83
    }
84
 
85
    /**
86
     * Gets the date at which the access token expires.
87
     *
88
     * @return the expiration date of the token
89
     */
90
    public Date getExpires() {
91
        return this.expires;
92
    }
93
 
94
    /**
95
     * Gets the list of permissions associated with this access token. Note that the most up-to-date
96
     * list of permissions is maintained by the Facebook service, so this list may be outdated if
97
     * permissions have been added or removed since the time the AccessToken object was created. For
98
     * more information on permissions, see https://developers.facebook.com/docs/reference/login/#permissions.
99
     *
100
     * @return a read-only list of strings representing the permissions granted via this access token
101
     */
102
    public List<String> getPermissions() {
103
        return this.permissions;
104
    }
105
 
106
    /**
107
     * Gets the list of permissions declined by the user with this access token.  It represents the entire set
108
     * of permissions that have been requested and declined.  Note that the most up-to-date list of permissions is
109
     * maintained by the Facebook service, so this list may be outdated if permissions have been granted or declined
110
     * since the last time an AccessToken object was created.
111
     *
112
     * @return a read-only list of strings representing the permissions declined by the user
113
     */
114
    public List<String> getDeclinedPermissions() {
115
        return this.declinedPermissions;
116
    }
117
 
118
    /**
119
     * Gets the {@link AccessTokenSource} indicating how this access token was obtained.
120
     *
121
     * @return the enum indicating how the access token was obtained
122
     */
123
    public AccessTokenSource getSource() {
124
        return source;
125
    }
126
 
127
    /**
128
     * Gets the date at which the token was last refreshed. Since tokens expire, the Facebook SDK
129
     * will attempt to renew them periodically.
130
     *
131
     * @return the date at which this token was last refreshed
132
     */
133
    public Date getLastRefresh() {
134
        return this.lastRefresh;
135
    }
136
 
137
    /**
138
     * Creates a new AccessToken using the supplied information from a previously-obtained access
139
     * token (for instance, from an already-cached access token obtained prior to integration with the
140
     * Facebook SDK).
141
     *
142
     * @param accessToken       the access token string obtained from Facebook
143
     * @param expirationTime    the expiration date associated with the token; if null, an infinite expiration time is
144
     *                          assumed (but will become correct when the token is refreshed)
145
     * @param lastRefreshTime   the last time the token was refreshed (or when it was first obtained); if null,
146
     *                          the current time is used.
147
     * @param accessTokenSource an enum indicating how the token was originally obtained (in most cases,
148
     *                          this will be either AccessTokenSource.FACEBOOK_APPLICATION or
149
     *                          AccessTokenSource.WEB_VIEW); if null, FACEBOOK_APPLICATION is assumed.
150
     * @param permissions       the permissions that were requested when the token was obtained (or when
151
     *                          it was last reauthorized); may be null if permission set is unknown
152
     * @return a new AccessToken
153
     */
154
    public static AccessToken createFromExistingAccessToken(String accessToken, Date expirationTime,
155
            Date lastRefreshTime, AccessTokenSource accessTokenSource, List<String> permissions) {
156
        if (expirationTime == null) {
157
            expirationTime = DEFAULT_EXPIRATION_TIME;
158
        }
159
        if (lastRefreshTime == null) {
160
            lastRefreshTime = DEFAULT_LAST_REFRESH_TIME;
161
        }
162
        if (accessTokenSource == null) {
163
            accessTokenSource = DEFAULT_ACCESS_TOKEN_SOURCE;
164
        }
165
 
166
        return new AccessToken(accessToken, expirationTime, permissions, null, accessTokenSource, lastRefreshTime);
167
    }
168
 
169
    /**
170
     * Creates a new AccessToken using the information contained in an Intent populated by the Facebook
171
     * application in order to launch a native link. For more information on native linking, please see
172
     * https://developers.facebook.com/docs/mobile/android/deep_linking/.
173
     *
174
     * @param intent the Intent that was used to start an Activity; must not be null
175
     * @return a new AccessToken, or null if the Intent did not contain enough data to create one
176
     */
177
    public static AccessToken createFromNativeLinkingIntent(Intent intent) {
178
        Validate.notNull(intent, "intent");
179
 
180
        if (intent.getExtras() == null) {
181
            return null;
182
        }
183
 
184
        return createFromBundle(null, intent.getExtras(), AccessTokenSource.FACEBOOK_APPLICATION_WEB, new Date());
185
    }
186
 
187
    @Override
188
    public String toString() {
189
        StringBuilder builder = new StringBuilder();
190
 
191
        builder.append("{AccessToken");
192
        builder.append(" token:").append(tokenToString());
193
        appendPermissions(builder);
194
        builder.append("}");
195
 
196
        return builder.toString();
197
    }
198
 
199
    static AccessToken createEmptyToken() {
200
        return new AccessToken("", ALREADY_EXPIRED_EXPIRATION_TIME, null, null, AccessTokenSource.NONE,
201
                DEFAULT_LAST_REFRESH_TIME);
202
    }
203
 
204
    static AccessToken createFromString(String token, List<String> permissions, AccessTokenSource source) {
205
        return new AccessToken(token, DEFAULT_EXPIRATION_TIME, permissions, null, source, DEFAULT_LAST_REFRESH_TIME);
206
    }
207
 
208
    static AccessToken createFromNativeLogin(Bundle bundle, AccessTokenSource source) {
209
        Date expires = getBundleLongAsDate(
210
                bundle, NativeProtocol.EXTRA_EXPIRES_SECONDS_SINCE_EPOCH, new Date(0));
211
        ArrayList<String> permissions = bundle.getStringArrayList(NativeProtocol.EXTRA_PERMISSIONS);
212
        String token = bundle.getString(NativeProtocol.EXTRA_ACCESS_TOKEN);
213
 
214
        return createNew(permissions, null, token, expires, source);
215
    }
216
 
217
    static AccessToken createFromWebBundle(List<String> requestedPermissions, Bundle bundle, AccessTokenSource source) {
218
        Date expires = getBundleLongAsDate(bundle, EXPIRES_IN_KEY, new Date());
219
        String token = bundle.getString(ACCESS_TOKEN_KEY);
220
 
221
        // With Login v4, we now get back the actual permissions granted, so update the permissions to be the real thing
222
        String grantedPermissions = bundle.getString("granted_scopes");
223
        if (!Utility.isNullOrEmpty(grantedPermissions)) {
224
            requestedPermissions =  new ArrayList<String>(Arrays.asList(grantedPermissions.split(",")));
225
        }
226
        String deniedPermissions = bundle.getString("denied_scopes");
227
        List<String> declinedPermissions = null;
228
        if (!Utility.isNullOrEmpty(deniedPermissions)) {
229
            declinedPermissions = new ArrayList<String>(Arrays.asList(deniedPermissions.split(",")));
230
        }
231
 
232
        return createNew(requestedPermissions, declinedPermissions, token, expires, source);
233
    }
234
 
235
    @SuppressLint("FieldGetter")
236
    static AccessToken createFromRefresh(AccessToken current, Bundle bundle) {
237
        // Only tokens obtained via SSO support refresh. Token refresh returns the expiration date in
238
        // seconds from the epoch rather than seconds from now.
239
        if (current.source != AccessTokenSource.FACEBOOK_APPLICATION_WEB &&
240
                current.source != AccessTokenSource.FACEBOOK_APPLICATION_NATIVE &&
241
                current.source != AccessTokenSource.FACEBOOK_APPLICATION_SERVICE) {
242
            throw new FacebookException("Invalid token source: " + current.source);
243
        }
244
 
245
        Date expires = getBundleLongAsDate(bundle, EXPIRES_IN_KEY, new Date(0));
246
        String token = bundle.getString(ACCESS_TOKEN_KEY);
247
 
248
        return createNew(current.getPermissions(), current.getDeclinedPermissions(), token, expires, current.source);
249
    }
250
 
251
    static AccessToken createFromTokenWithRefreshedPermissions(
252
            AccessToken token,
253
            List<String> grantedPermissions,
254
            List<String> declinedPermissions) {
255
        return new AccessToken(token.token, token.expires, grantedPermissions, declinedPermissions, token.source, token.lastRefresh);
256
    }
257
 
258
    private static AccessToken createNew(
259
            List<String> grantedPermissions,
260
            List<String> declinedPermissions,
261
            String accessToken, Date expires,
262
            AccessTokenSource source) {
263
        if (Utility.isNullOrEmpty(accessToken) || (expires == null)) {
264
            return createEmptyToken();
265
        } else {
266
            return new AccessToken(accessToken, expires, grantedPermissions, declinedPermissions, source, new Date());
267
        }
268
    }
269
 
270
    static AccessToken createFromCache(Bundle bundle) {
271
        List<String> permissions = getPermissionsFromBundle(bundle, TokenCachingStrategy.PERMISSIONS_KEY);
272
        List<String> declinedPermissions = getPermissionsFromBundle(bundle, TokenCachingStrategy.DECLINED_PERMISSIONS_KEY);
273
 
274
        return new AccessToken(bundle.getString(TokenCachingStrategy.TOKEN_KEY), TokenCachingStrategy.getDate(bundle,
275
                TokenCachingStrategy.EXPIRATION_DATE_KEY), permissions, declinedPermissions,
276
                TokenCachingStrategy.getSource(bundle),
277
                TokenCachingStrategy.getDate(bundle, TokenCachingStrategy.LAST_REFRESH_DATE_KEY));
278
    }
279
 
280
    static List<String> getPermissionsFromBundle(Bundle bundle, String key) {
281
        // Copy the list so we can guarantee immutable
282
        List<String> originalPermissions = bundle.getStringArrayList(key);
283
        List<String> permissions;
284
        if (originalPermissions == null) {
285
            permissions = Collections.emptyList();
286
        } else {
287
            permissions = Collections.unmodifiableList(new ArrayList<String>(originalPermissions));
288
        }
289
        return permissions;
290
    }
291
 
292
    Bundle toCacheBundle() {
293
        Bundle bundle = new Bundle();
294
 
295
        bundle.putString(TokenCachingStrategy.TOKEN_KEY, this.token);
296
        TokenCachingStrategy.putDate(bundle, TokenCachingStrategy.EXPIRATION_DATE_KEY, expires);
297
        bundle.putStringArrayList(TokenCachingStrategy.PERMISSIONS_KEY, new ArrayList<String>(permissions));
298
        bundle.putStringArrayList(TokenCachingStrategy.DECLINED_PERMISSIONS_KEY, new ArrayList<String>(declinedPermissions));
299
        bundle.putSerializable(TokenCachingStrategy.TOKEN_SOURCE_KEY, source);
300
        TokenCachingStrategy.putDate(bundle, TokenCachingStrategy.LAST_REFRESH_DATE_KEY, lastRefresh);
301
 
302
        return bundle;
303
    }
304
 
305
    boolean isInvalid() {
306
        return Utility.isNullOrEmpty(this.token) || new Date().after(this.expires);
307
    }
308
 
309
    private static AccessToken createFromBundle(List<String> requestedPermissions, Bundle bundle,
310
            AccessTokenSource source,
311
            Date expirationBase) {
312
        String token = bundle.getString(ACCESS_TOKEN_KEY);
313
        Date expires = getBundleLongAsDate(bundle, EXPIRES_IN_KEY, expirationBase);
314
 
315
        if (Utility.isNullOrEmpty(token) || (expires == null)) {
316
            return null;
317
        }
318
 
319
        return new AccessToken(token, expires, requestedPermissions, null, source, new Date());
320
    }
321
 
322
    private String tokenToString() {
323
        if (this.token == null) {
324
            return "null";
325
        } else if (Settings.isLoggingBehaviorEnabled(LoggingBehavior.INCLUDE_ACCESS_TOKENS)) {
326
            return this.token;
327
        } else {
328
            return "ACCESS_TOKEN_REMOVED";
329
        }
330
    }
331
 
332
    private void appendPermissions(StringBuilder builder) {
333
        builder.append(" permissions:");
334
        if (this.permissions == null) {
335
            builder.append("null");
336
        } else {
337
            builder.append("[");
338
            builder.append(TextUtils.join(", ", permissions));
339
            builder.append("]");
340
        }
341
    }
342
 
343
    private static class SerializationProxyV1 implements Serializable {
344
        private static final long serialVersionUID = -2488473066578201069L;
345
        private final Date expires;
346
        private final List<String> permissions;
347
        private final String token;
348
        private final AccessTokenSource source;
349
        private final Date lastRefresh;
350
 
351
        private SerializationProxyV1(String token, Date expires,
352
                List<String> permissions, AccessTokenSource source, Date lastRefresh) {
353
            this.expires = expires;
354
            this.permissions = permissions;
355
            this.token = token;
356
            this.source = source;
357
            this.lastRefresh = lastRefresh;
358
        }
359
 
360
        private Object readResolve() {
361
            return new AccessToken(token, expires, permissions, null, source, lastRefresh);
362
        }
363
    }
364
 
365
    private static class SerializationProxyV2 implements Serializable {
366
        private static final long serialVersionUID = -2488473066578201068L;
367
        private final Date expires;
368
        private final List<String> permissions;
369
        private final List<String> declinedPermissions;
370
        private final String token;
371
        private final AccessTokenSource source;
372
        private final Date lastRefresh;
373
 
374
        private SerializationProxyV2(String token, Date expires,
375
                                     List<String> permissions, List<String> declinedPermissions,
376
                                     AccessTokenSource source, Date lastRefresh) {
377
            this.expires = expires;
378
            this.permissions = permissions;
379
            this.declinedPermissions = declinedPermissions;
380
            this.token = token;
381
            this.source = source;
382
            this.lastRefresh = lastRefresh;
383
        }
384
 
385
        private Object readResolve() {
386
            return new AccessToken(token, expires, permissions, declinedPermissions, source, lastRefresh);
387
        }
388
    }
389
 
390
    private Object writeReplace() {
391
        return new SerializationProxyV2(token, expires, permissions, declinedPermissions, source, lastRefresh);
392
    }
393
 
394
    // have a readObject that throws to prevent spoofing
395
    private void readObject(ObjectInputStream stream) throws InvalidObjectException {
396
        throw new InvalidObjectException("Cannot readObject, serialization proxy required");
397
    }
398
 
399
 
400
    private static Date getBundleLongAsDate(Bundle bundle, String key, Date dateBase) {
401
        if (bundle == null) {
402
            return null;
403
        }
404
 
405
        long secondsFromBase = Long.MIN_VALUE;
406
 
407
        Object secondsObject = bundle.get(key);
408
        if (secondsObject instanceof Long) {
409
            secondsFromBase = (Long) secondsObject;
410
        } else if (secondsObject instanceof String) {
411
            try {
412
                secondsFromBase = Long.parseLong((String) secondsObject);
413
            } catch (NumberFormatException e) {
414
                return null;
415
            }
416
        } else {
417
            return null;
418
        }
419
 
420
        if (secondsFromBase == 0) {
421
            return new Date(Long.MAX_VALUE);
422
        } else {
423
            return new Date(dateBase.getTime() + (secondsFromBase * 1000L));
424
        }
425
    }
426
}