//
// Copyright (c) 2014 VK.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
package com.vk.sdk;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import android.text.TextUtils;
import com.vk.sdk.api.VKParameters;
import com.vk.sdk.util.VKStringJoiner;
import com.vk.sdk.util.VKUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* Presents VK API access token that used for loading API methods and other stuff.
*/
public class VKAccessToken {
public static final String ACCESS_TOKEN = "access_token";
public static final String EXPIRES_IN = "expires_in";
public static final String USER_ID = "user_id";
public static final String SECRET = "secret";
public static final String HTTPS_REQUIRED = "https_required";
public static final String CREATED = "created";
public static final String SUCCESS = "success";
public static final String EMAIL = "email";
public static final String SCOPE = "scope";
/**
* String token for use in request parameters
*/
public String accessToken = null;
/**
* Time when token expires
*/
public int expiresIn = 0;
/**
* Current user id for this token
*/
public String userId = null;
/**
* User secret to sign requests (if nohttps used)
*/
public String secret = null;
/**
* If user sets "Always use HTTPS" setting in his profile, it will be true
*/
public boolean httpsRequired = false;
/**
* Indicates time of token creation
*/
public long created = 0;
/**
* User email
*/
public String email = null;
/**
* Token scope
*/
private Map<String, Boolean> scope = null;
/**
* Save token into specified file
*
* @param filePath path to file with saved token
*/
public void saveTokenToFile(String filePath) {
VKUtil.stringToFile(filePath, serialize());
}
/**
* Save token into shared preferences with key
*
* @param ctx Context for preferences
* @param tokenKey Key for saving settings
*/
public void saveTokenToSharedPreferences(Context ctx, String tokenKey) {
if (ctx == null) {
return;
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
SharedPreferences.Editor edit = prefs.edit();
edit.putString(tokenKey, serialize());
edit.apply();
}
/**
* Removes token from preferences with specified key
* @param ctx Context for preferences
* @param tokenKey Key for saving settings
*/
public static void removeTokenAtKey(Context ctx, String tokenKey) {
if (ctx == null) {
return;
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
SharedPreferences.Editor edit = prefs.edit();
edit.remove(tokenKey);
edit.apply();
}
private VKAccessToken() {
}
/**
* Serialize token to VKParameters
* @return serialized VKParameters value
*/
protected Map<String, String> tokenParams() {
Map<String, String> params = new HashMap<>();
params.put(ACCESS_TOKEN, accessToken);
params.put(EXPIRES_IN, "" + expiresIn);
params.put(USER_ID, userId);
params.put(CREATED, "" + created);
if (scope != null) {
params.put(SCOPE, TextUtils.join(",", scope.keySet()));
}
if (secret != null) {
params.put(SECRET, secret);
}
if (httpsRequired) {
params.put(HTTPS_REQUIRED, "1");
}
if (email != null) {
params.put(EMAIL, email);
}
return params;
}
/**
* Serialize token into string
*
* @return Serialized token string as query-string
*/
protected String serialize() {
return VKStringJoiner.joinParams(tokenParams());
}
/**
* Retrieve token from key-value query string
*
* @param urlString string that contains URL-query part with token. E.g. access_token=eee&expires_in=0...
* @return parsed token
*/
public static VKAccessToken tokenFromUrlString(String urlString) {
if (urlString == null)
return null;
Map<String, String> parameters = VKUtil.explodeQueryString(urlString);
return tokenFromParameters(parameters);
}
/**
* Retrieve token from key-value map
*
* @param parameters map that contains token info
* @return Parsed token
*/
public static VKAccessToken tokenFromParameters(@Nullable Map<String, String> parameters) {
if (parameters == null || parameters.size() == 0) {
return null;
}
VKAccessToken token = new VKAccessToken();
try {
token.accessToken = parameters.get(ACCESS_TOKEN);
token.userId = parameters.get(USER_ID);
token.secret = parameters.get(SECRET);
token.email = parameters.get(EMAIL);
token.httpsRequired = false;
if (parameters.get(EXPIRES_IN) != null) { token.expiresIn = Integer.parseInt(parameters.get(EXPIRES_IN)); }
String scope = parameters.get(SCOPE);
if (scope != null) {
HashMap<String, Boolean> scopeMap = new HashMap<>();
for (String s : scope.split(",")) {
scopeMap.put(s, true);
}
token.scope = scopeMap;
}
if (parameters.containsKey(HTTPS_REQUIRED)) {
token.httpsRequired = parameters.get(HTTPS_REQUIRED).equals("1");
} else if (token.secret == null) {
token.httpsRequired = true;
}
if (parameters.containsKey(CREATED)) {
token.created = Long.parseLong(parameters.get(CREATED));
} else {
token.created = System.currentTimeMillis();
}
return token.accessToken != null ? token : null;
} catch (Exception e) {
return null;
}
}
/**
* Retrieves token from shared preferences
*
* @param ctx Context for preferences
* @param key Key for retrieve token
* @return Previously saved token or null
*/
public static VKAccessToken tokenFromSharedPreferences(Context ctx, String key) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return tokenFromUrlString(prefs.getString(key, null));
}
/**
* Retrieve token from file. Token must be saved into file with saveTokenToFile method
*
* @param filePath path to file with saved token
* @return Previously saved token or null
*/
public static VKAccessToken tokenFromFile(String filePath) {
try {
String data = VKUtil.fileToString(filePath);
return tokenFromUrlString(data);
} catch (Exception e) {
return null;
}
}
/**
* Checks expiration time of token and returns result.
*
* @return true if token has expired, false otherwise.
*/
public boolean isExpired() {
return expiresIn > 0 && expiresIn * 1000 + created < System.currentTimeMillis();
}
private static final String VK_SDK_ACCESS_TOKEN_PREF_KEY = "VK_SDK_ACCESS_TOKEN_PLEASE_DONT_TOUCH";
private volatile static VKAccessToken sCurrentToken;
/**
* @return Returns shared instance of current access token
*/
public static VKAccessToken currentToken() {
if (sCurrentToken == null) {
synchronized (VKAccessToken.class) {
if (sCurrentToken == null) {
sCurrentToken = VKAccessToken.tokenFromSharedPreferences(VKUIHelper.getApplicationContext(), VK_SDK_ACCESS_TOKEN_PREF_KEY);
}
}
}
return sCurrentToken;
}
/**
* Replaces token with new token, and saves it to shared preferences of application
* @param newToken New access token to set. If null, removes old token from preferences
* @return old value of access token
*/
static VKAccessToken replaceToken(@NonNull Context ctx, @Nullable VKAccessToken newToken) {
VKAccessToken oldToken = sCurrentToken;
sCurrentToken = newToken;
if (sCurrentToken != null) {
sCurrentToken.save();
} else {
removeTokenAtKey(ctx, VK_SDK_ACCESS_TOKEN_PREF_KEY);
}
return oldToken;
}
/**
* Saves this token into application shared preferences
*/
public void save() {
saveTokenToSharedPreferences(VKUIHelper.getApplicationContext(), VK_SDK_ACCESS_TOKEN_PREF_KEY);
}
/**
* Return if this token contains passed permissions array
* @param scopes permissions to check
* @return true, if token has all of this permissions
*/
public boolean hasScope(String... scopes) {
boolean allScope = true;
for (String scopeStr : scopes) {
if (this.scope.get(scopeStr) == null) {
allScope = false;
break;
}
}
return allScope;
}
/**
* Creates copy of current token, with params from passed token
* @param token Usually this is partly filled access token, made after validation
* @return New access token with updated fields
*/
public VKAccessToken copyWithToken(@NonNull VKAccessToken token) {
Map<String, String> newTokenParams = tokenParams();
newTokenParams.putAll(token.tokenParams());
return VKAccessToken.tokenFromParameters(newTokenParams);
}
}