/**
* Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
* Created by ddrobins on 7/28/15.
*/
/**
* Class used to authorize a mobile user against Oracle Mobile Cloud Service. Callers should use
* MobileBackend's [authorization]{@link MobileBackend#authorization} property.
* Derives from {@link Authorization}.
* @constructor
* @global
*/
function OAuthAuthorization(config, backend, appKey, utils, platform, logger) {
Authorization.call(this, backend, appKey, utils, platform, logger);
var HEADERS = utils.HEADERS;
var _clientId = utils.validateConfiguration(config.clientId);
var _clientSecret = utils.validateConfiguration(config.clientSecret);
if(config.hasOwnProperty("userIdentityDomainName")){
var _tenantName = utils.validateConfiguration(config.userIdentityDomainName);
}
var _tokenExpiredTime = null;
var _authorizedUserName = null;
var _this = this;
/**
* Returns the username of the current authorized user if any, null otherwise.
* @type {String}
*/
this.getAuthorizedUserName = function(){
return _authorizedUserName;
};
/**
* Returns the client ID for the current backend.
*/
this.getClientId = function(){
return _clientId;
};
/**
* Returns the tenant name for the current backend.
*/
this.getTenantName = function(){
return _tenantName;
};
/**
* Returns the client secret for the current backend.
*/
this.getClientSecret= function(){
return _clientSecret;
};
/**
* Authenticates a user with the given credentials against the service. The user remains logged in until logout() is called.
* @param username {String} The username of the credentials.
* @param password {String} The password of the credentials.
* @param [successCallback] {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead).
* @param [errorCallback] {Authorization~authenticateErrorCallback} Optional callback invoked on failure (deprecated use promises instead).
* @return {Promise.<NetworkResponse|NetworkResponse>}
*/
this.authenticate = function (username, password, successCallback, errorCallback) {
this.logout();
if(!username || !password){
logger.error("Wrong username or password parameter");
if(errorCallback){
errorCallback(400, 'Bad Request');
return undefined;
} else {
return Promise.reject(new NetworkResponse(400, 'Bad Request'));
}
}
var authorizationToken = "Basic " + utils.encodeBase64(this.getClientId() + ":" + this.getClientSecret());
var requestBody = urlEncodeComponent(username, password);
var headers = {};
headers[HEADERS.CONTENT_TYPE] = 'application/x-www-form-urlencoded; charset=utf-8';
headers[HEADERS.AUTHORIZATION] = authorizationToken;
headers[HEADERS.ORACLE_MOBILE_APPLICATION_KEY] = this._getApplicationKey();
headers[HEADERS.ORACLE_MOBILE_CLIENT_SDK_INFO] = backend.getClientSDKInfoHeader(utils.MODULE_NAMES.AUTHORIZATION);
if(typeof _this.getTenantName() !== 'undefined'){
headers[HEADERS.X_USER_IDENTITY_DOMAIN_NAME] = _this.getTenantName();
}
return platform.invokeService({
url: backend.getOAuthTokenUrl(),
method: utils.HTTP_METHODS.POST,
headers: headers,
data: requestBody
})
.then(invokeServiceSuccess, invokeServiceError);
function invokeServiceSuccess(response){
_this._authenticateSuccess(response, response.data.access_token);
_authorizedUserName = username;
if (successCallback) {
successCallback(response.statusCode, response.data);
}
return response;
}
function invokeServiceError(response){
_this._authenticateError(response);
if (errorCallback) {
errorCallback(response.statusCode, response.data);
} else {
return Promise.reject(response);
}
}
};
/**
* Authenticates an anonymous user against the service. The user remains logged in until logout() is called.
* @param [successCallback] {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead).
* @param [errorCallback] {Authorization~errorCallback} Optional callback invoked on failure (deprecated use promises instead).
* @return {Promise.<NetworkResponse|NetworkResponse>}
*/
this.authenticateAnonymous = function (successCallback, errorCallback) {
var authorizationToken = "Basic " + utils.encodeBase64(this.getClientId() + ":" + this.getClientSecret());
var headers = {};
headers[HEADERS.CONTENT_TYPE] = 'application/x-www-form-urlencoded; charset=utf-8';
if (typeof _this.getTenantName() !== 'undefined') {
headers[HEADERS.X_USER_IDENTITY_DOMAIN_NAME] = this.getTenantName();
}
return this._authenticateAnonymousInvoke(authorizationToken,
headers,
backend.getOAuthTokenUrl(),
utils.HTTP_METHODS.POST,
'grant_type=client_credentials')
.then(invokeServiceSuccess, invokeServiceError);
function invokeServiceSuccess(response) {
_tokenExpiredTime = Date.now() + response.data.expires_in * 1000;
if (successCallback) {
successCallback(response.statusCode, response.data);
}
return response;
}
function invokeServiceError(response) {
if(errorCallback) {
errorCallback(response.statusCode, response.data);
} else {
return Promise.reject(response);
}
}
};
this._anonymousTokenResponseConverter = function(response){
return { orgResponse: response.orgResponse, anonymousAccessToken: response.orgResponse.data.access_token };
};
/**
* Checks to see if the OAuth token is null, undefined, NaN,an empty string (""), 0,or false. It also checks the timestamp
* for when the token was first retrieved to see if it was still valid.
* @returns {Boolean}
*/
this.isTokenValid = function () {
if (this.getAccessToken() || this._getAnonymousAccessToken()) {
logger.verbose("Token is not null or empty");
var currentTime = Date.now();
if (currentTime >= _tokenExpiredTime) {
logger.info("Token has expired");
return false;
}
else {
logger.verbose("Token is still valid");
return true;
}
} else {
return false;
}
};
/**
* Logs out the current user and clears credentials and tokens.
*/
this.logout = function() {
this._clearState();
};
/**
* For OAuth, the SDK can not refresh because it does not persist client credentials.
* This function only exists here because it inherits from the Authorization object, which is also used for other types of authentication in which the token can expire.
* @param successCallback {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead).
* @param errorCallback {Authorization~errorCallback} Optional callback invoked on failure (deprecated use promises instead).
* @return {Promise.<String|NetworkResponse>}
*/
this.refreshToken = function(successCallback, errorCallback) {
var isTokenValid = this.isTokenValid();
if (isTokenValid && !this.getAccessToken() && this._getIsAnonymous()) {
if (successCallback) {
logger.error("Anonymous token is valid, you do not need to refresh.");
successCallback(200, this._getAnonymousAccessToken());
}
return Promise.resolve(new NetworkResponse(200, this._getAnonymousAccessToken()));
} else if (isTokenValid && !this._getAnonymousAccessToken() && !this._getIsAnonymous()) {
if (successCallback) {
logger.error("Authenticated token is valid, you do not need to refresh.");
successCallback(200, this.getAccessToken());
}
return Promise.resolve(new NetworkResponse(200, this._getAnonymousAccessToken()));
} else {
logger.error("Token has expired or user has not been authenticate with the service.");
if (errorCallback) {
errorCallback(401, "Please use the authenticate with username/password combination or authenticateAnonymous function before using refreshToken.")
}
return Promise.resolve(new NetworkResponse(401, "Please use the authenticate with username/password combination or authenticateAnonymous function before using refreshToken."));
}
};
var baseClearState = this._clearState;
this._clearState = function(){
baseClearState.call(this);
_authorizedUserName = null;
_tokenExpiredTime = Date.now() * 1000;
};
function urlEncodeComponent(user,pass){
var username;
var password;
if(user.indexOf("@") > -1){
username = encodeURIComponent(user).replace(/%20/g,'+');
}
else{
username = encodeURIComponent(user).replace(/%5B/g, '[').replace(/%5D/g, ']');
}
if(pass.indexOf("&") > -1){
password = encodeURIComponent(pass).replace(/%20/g,'+');
}
else{
password = encodeURIComponent(pass).replace(/%5B/g, '[').replace(/%5D/g, ']');
}
return "grant_type=password&username=" + username +"&password=" + password;
}
this._getHttpHeaders = function (headers) {
if (this.getAccessToken() !== null && typeof this.getAccessToken() == "string") {
headers[HEADERS.AUTHORIZATION] = "Bearer " + this.getAccessToken();
}
headers[HEADERS.ORACLE_MOBILE_APPLICATION_KEY]= this._getApplicationKey();
};
this._getAnonymousHttpHeaders = function (headers) {
if (this._getAnonymousAccessToken() && typeof this._getAnonymousAccessToken() == "string") {
headers[HEADERS.AUTHORIZATION] = "Bearer " + this._getAnonymousAccessToken();
}
headers[HEADERS.ORACLE_MOBILE_APPLICATION_KEY] = this._getApplicationKey();
};
}
OAuthAuthorization.prototype = Object.create(Authorization.prototype);
OAuthAuthorization.prototype.constructor = OAuthAuthorization;