/** * 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;