Source: authorization/oauth-authorization.js

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