Search code examples
google-apps-scriptlooker-studio

Google Data Studio connector authenticate at third party


I'm building a Google Data Studio connector to a third party source, Siteimprove. Siteimprove has an api which requires Basic Access Authentication.

I've setup the authentication for username and token (I've also tried username and password) in my google apps script, with all the required functions based on the documentation

-edit- as requested the full code for these functions

/**
 * Returns the Auth Type of this connector.
 * @return {object} The Auth type.
 */
function getAuthType() {
  var cc = DataStudioApp.createCommunityConnector();
  return cc.newAuthTypeResponse()
    .setAuthType(cc.AuthType.USER_TOKEN)
    .setHelpUrl('http://developer.siteimprove.com/v1/get-access/')
    .build();
}

/**
 * Resets the auth service.
 */
function resetAuth() {
  var user_tokenProperties = PropertiesService.getUserProperties();
  user_tokenProperties.deleteProperty('dscc.username');
  user_tokenProperties.deleteProperty('dscc.password');
}

/**
 * Returns true if the auth service has access.
 * @return {boolean} True if the auth service has access.
 */
function isAuthValid() {
  var userProperties = PropertiesService.getUserProperties();
  var userName = userProperties.getProperty('dscc.username');
  var token = userProperties.getProperty('dscc.token');
  // This assumes you have a validateCredentials function that
  // can validate if the userName and token are correct.
  return validateCredentials(userName, token);
}

/**
 * Sets the credentials.
 * @param {Request} request The set credentials request.
 * @return {object} An object with an errorCode.
 */
function setCredentials(request) {
  var creds = request.userToken;
  var username = creds.username;
  var token = creds.token;

  // Optional
  // Check if the provided username and token are valid through a
  // call to your service. You would have to have a `checkForValidCreds`
  // function defined for this to work.
  var validCreds = validateCredentials(username, token);
  if (!validCreds) {
    return {
      errorCode: 'INVALID_CREDENTIALS'
    };
  }
  var userProperties = PropertiesService.getUserProperties();
  userProperties.setProperty('dscc.username', username);
  userProperties.setProperty('dscc.token', token);
  return {
    errorCode: 'NONE'
  };
}

function validateCredentials(userName,token){

  var headers = {
    "Authorization" : "Basic " + Utilities.base64Encode(userName + ':' + token)
  };

  var params = {
    "method":"GET",
    "headers":headers
  };

  var response = UrlFetchApp.fetch("https://api.siteimprove.com/v2/", params);
  return response;
  console.log(response);
}

And the manifest file

{
  "dataStudio": {
    "name": "Connector for Siteimprove",
    "company": "<company name>",
    "logoUrl": "<company logo url>",
    "addonUrl": "",
    "supportUrl": "",
    "description": "This connector can be used to show basic data from Siteimprove"
  }
}

When I run the script I get a prompt for credentials, but this is a prompt to connect with a google account connect with a google account

But I need a way to provide credentials for a third party service. If I use my google account I get a 401 response from the Siteimprove API, so that seems to work as expected.

Any clues how I would get a prompt to provide credentials for a third party service?


Solution

  • /**
    * Returns the Auth Type of this connector.
    * @return {object} The Auth type.
    */
    function getAuthType() {
      var cc = DataStudioApp.createCommunityConnector();
      return cc.newAuthTypeResponse()
        .setAuthType(cc.AuthType.USER_PASS)
        .setHelpUrl('http://developer.siteimprove.com/v1/get-access/')
        .build();
    }
    
    /**
    * Resets the auth service.
    */
    function resetAuth() {
      var user_tokenProperties = PropertiesService.getUserProperties();
      user_tokenProperties.deleteProperty('dscc.username');
      user_tokenProperties.deleteProperty('dscc.password');
    }
    
    /**
    * Returns true if the auth service has access.
    * @return {boolean} True if the auth service has access.
    */
    function isAuthValid() {
      const usernameAndPassword = loadCurrentUsernameAndPassword();
      return usernameAndPassword.username && usernameAndPassword.password && validateCredentials(usernameAndPassword.username, usernameAndPassword.password)
    };
    
    function loadCurrentUsernameAndPassword() {
      const properties = PropertiesService.getUserProperties();
      return {
        username: properties.getProperty('dscc.username'),
        password: properties.getProperty('dscc.password')
      }
    };
    
    function setCredentials(request) {
      var isCredentialsValid = validateCredentials(request.userPass.username, request.userPass.password);
      if (!isCredentialsValid) {
        return {
          errorCode: "INVALID_CREDENTIALS"
        };
      } else {
        storeUsernameAndPassword(request.userPass.username, request.userPass.password);
        return {
          errorCode: "NONE"
        };
      }
    };
    
    function validateCredentials(username, password) {
      var rawResponse = UrlFetchApp.fetch('https://api.siteimprove.com/v2', {
          method: 'GET',
          headers: {
                  'Authorization': 'Basic ' + Utilities.base64Encode(username + ':' + password)
          },
          muteHttpExceptions: true
        });
    
        return rawResponse.getResponseCode() === 200;
      }
    
      function storeUsernameAndPassword(username, password) {
        PropertiesService
          .getUserProperties()
          .setProperty('dscc.username', username)
          .setProperty('dscc.password', password);
      };