Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion samples/AdobeSign.gs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ function getService_(optApiAccessPoint) {
.setCallbackFunction('authCallback')

// Set the property store where authorized tokens should be persisted.
// Use per-user properties only: script properties are shared by every
// user of a deployment, which would leak one user's tokens and stored
// API access point to all other users.
.setPropertyStore(PropertiesService.getUserProperties())
.setPropertyStore(PropertiesService.getScriptProperties())

// Set the scopes.
.setScope('user_read');
Expand All @@ -82,6 +84,13 @@ function getService_(optApiAccessPoint) {
function authCallback(request) {
// Get the API access point specified in the URL parameters.
var apiAccessPoint = request.parameter[API_ACCESS_POINT_KEY];
// Only accept API access points on Adobe-owned hosts. This parameter is
// attacker-controllable in web-app/add-on deployments, and an unchecked
// value sends the token exchange (code, client_id, client_secret) and all
// later refreshes to an arbitrary host.
if (apiAccessPoint && !isValidApiAccessPoint_(apiAccessPoint)) {
return HtmlService.createHtmlOutput('Denied.');
}
var service = getService_(apiAccessPoint);
var authorized = service.handleCallback(request);
if (authorized) {
Expand All @@ -93,6 +102,21 @@ function authCallback(request) {
}
}

/**
* Determines whether the given API access point is on an Adobe-owned host.
* @param {string} apiAccessPoint The API access point URL to validate.
* @return {boolean} True if the host is an Adobe domain served over HTTPS.
*/
function isValidApiAccessPoint_(apiAccessPoint) {
var match = /^https:\/\/([a-zA-Z0-9.-]+)(\/|$)/.exec(apiAccessPoint);
if (!match) {
return false;
}
var host = match[1].toLowerCase();
return /(^|\.)adobe\.com$/.test(host) || /(^|\.)adobesign\.com$/.test(host) ||
/(^|\.)echosign\.com$/.test(host);
}

/**
* Logs the redict URI to register in the Dropbox application settings.
*/
Expand Down
16 changes: 16 additions & 0 deletions samples/ZohoCRM.gs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ function getService_(optAccountServer) {
*/
function authCallback(request) {
var accountServer = request.parameter['accounts-server'];
// Only accept account servers on Zoho-owned hosts. This parameter is
// attacker-controllable in web-app/add-on deployments, and an unchecked
// value sends the token exchange (code, client_id, client_secret) and all
// later refreshes to an arbitrary host.
if (accountServer && !isValidAccountServer_(accountServer)) {
return HtmlService.createHtmlOutput('Denied.');
}
var service = getService_(accountServer);
var authorized = service.handleCallback(request);
if (authorized) {
Expand All @@ -92,6 +99,15 @@ function authCallback(request) {
}
}

/**
* Determines whether the given account server is a Zoho accounts host.
* @param {string} accountServer The account server URL to validate.
* @return {boolean} True if it is an https accounts.zoho.* host.
*/
function isValidAccountServer_(accountServer) {
return /^https:\/\/accounts\.zoho\.[a-z.]+\/?$/.test(accountServer);
}

/**
* Logs the redict URI to register.
*/
Expand Down