/**
* Copyright 2012 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.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
final class AccessToken implements Serializable {
private static final long serialVersionUID = 1L;
static final String ACCESS_TOKEN_KEY = "access_token";
static final String EXPIRES_IN_KEY = "expires_in";
private static final Date MIN_DATE = new Date(Long.MIN_VALUE);
private static final Date MAX_DATE = new Date(Long.MAX_VALUE);
private final Date expires;
private final List<String> permissions;
private final String token;
private final boolean isSSO;
private final Date lastRefresh;
AccessToken(String token, Date expires, List<String> permissions, boolean isSSO, Date lastRefresh) {
this.expires = expires;
this.permissions = permissions;
this.token = token;
this.isSSO = isSSO;
this.lastRefresh = lastRefresh;
}
String getToken() {
return this.token;
}
Date getExpires() {
return this.expires;
}
List<String> getPermissions() {
return this.permissions;
}
boolean getIsSSO() {
return this.isSSO;
}
Date getLastRefresh() {
return this.lastRefresh;
}
static AccessToken createEmptyToken(List<String> permissions) {
return new AccessToken("", MIN_DATE, permissions, false, MIN_DATE);
}
static AccessToken createFromString(String token, List<String> permissions) {
return new AccessToken(token, MAX_DATE, permissions, false, new Date());
}
static AccessToken createFromDialog(List<String> requestedPermissions, Bundle bundle) {
return createNew(requestedPermissions, bundle, false, new Date());
}
static AccessToken createFromSSO(List<String> requestedPermissions, Intent data) {
return createNew(requestedPermissions, data.getExtras(), true, new Date());
}
@SuppressLint("FieldGetter")
static AccessToken createForRefresh(AccessToken current, Bundle bundle) {
// isSSO is set true since only SSO tokens support refresh. Token refresh returns the expiration date in
// seconds from the epoch rather than seconds from now.
return createNew(current.getPermissions(), bundle, true, new Date(0));
}
private static AccessToken createNew(List<String> requestedPermissions, Bundle bundle, boolean isSSO,
Date expirationBase) {
String token = bundle.getString(ACCESS_TOKEN_KEY);
Date expires = getExpiresInDate(bundle, expirationBase);
if (Utility.isNullOrEmpty(token) || (expires == null)) {
return null;
}
return new AccessToken(token, expires, requestedPermissions, isSSO, new Date());
}
static AccessToken createFromCache(Bundle bundle) {
// Copy the list so we can guarantee immutable
List<String> originalPermissions = bundle.getStringArrayList(TokenCache.PERMISSIONS_KEY);
List<String> permissions;
if (originalPermissions == null) {
permissions = Collections.emptyList();
} else {
permissions = Collections.unmodifiableList(new ArrayList<String>(originalPermissions));
}
return new AccessToken(bundle.getString(TokenCache.TOKEN_KEY), TokenCache.getDate(bundle,
TokenCache.EXPIRATION_DATE_KEY), permissions, bundle.getBoolean(TokenCache.IS_SSO_KEY),
TokenCache.getDate(bundle, TokenCache.LAST_REFRESH_DATE_KEY));
}
Bundle toCacheBundle() {
Bundle bundle = new Bundle();
bundle.putString(TokenCache.TOKEN_KEY, this.token);
TokenCache.putDate(bundle, TokenCache.EXPIRATION_DATE_KEY, expires);
bundle.putStringArrayList(TokenCache.PERMISSIONS_KEY, new ArrayList<String>(permissions));
bundle.putBoolean(TokenCache.IS_SSO_KEY, isSSO);
TokenCache.putDate(bundle, TokenCache.LAST_REFRESH_DATE_KEY, lastRefresh);
return bundle;
}
boolean isInvalid() {
return Utility.isNullOrEmpty(this.token) || new Date().after(this.expires);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{AccessToken");
builder.append(" token:").append(tokenToString());
appendPermissions(builder);
builder.append("}");
return builder.toString();
}
private String tokenToString() {
if (this.token == null) {
return "null";
} else if (Settings.isLoggingBehaviorEnabled(LoggingBehaviors.INCLUDE_ACCESS_TOKENS)) {
return this.token;
} else {
return "ACCESS_TOKEN_REMOVED";
}
}
private void appendPermissions(StringBuilder builder) {
builder.append(" permissions:");
if (this.permissions == null) {
builder.append("null");
} else {
builder.append("[");
for (int i = 0; i < this.permissions.size(); i++) {
if (i > 0) {
builder.append(", ");
}
builder.append(this.permissions.get(i));
}
}
}
private static class SerializationProxyV1 implements Serializable {
private static final long serialVersionUID = -2488473066578201069L;
private final Date expires;
private final List<String> permissions;
private final String token;
private final boolean isSSO;
private final Date lastRefresh;
private SerializationProxyV1(String token, Date expires,
List<String> permissions, boolean isSSO, Date lastRefresh) {
this.expires = expires;
this.permissions = permissions;
this.token = token;
this.isSSO = isSSO;
this.lastRefresh = lastRefresh;
}
private Object readResolve() {
return new AccessToken(token, expires, permissions, isSSO, lastRefresh);
}
}
private Object writeReplace() {
return new SerializationProxyV1(token, expires, permissions, isSSO, lastRefresh);
}
// have a readObject that throws to prevent spoofing
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Cannot readObject, serialization proxy required");
}
private static Date getExpiresInDate(Bundle bundle, Date expirationBase) {
if (bundle == null) {
return null;
}
long secondsFromBase = Long.MIN_VALUE;
Object secondsObject = bundle.get(EXPIRES_IN_KEY);
if (secondsObject instanceof Long) {
secondsFromBase = (Long)secondsObject;
} else if (secondsObject instanceof String) {
try {
secondsFromBase = Long.parseLong((String)secondsObject);
} catch (NumberFormatException e) {
return null;
}
} else {
return null;
}
if (secondsFromBase == 0) {
return new Date(Long.MAX_VALUE);
} else {
return new Date(expirationBase.getTime() + (secondsFromBase * 1000L));
}
}
}