blob: fcaafac6cd6049c10da9b1109acdfd705bb0253c [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Implements a check whether an app id lists an origin.
*/
'use strict';
/**
* Parses the text as JSON and returns it as an array of strings.
* @param {string} text Input JSON
* @return {!Array.<string>} Array of origins
*/
function getOriginsFromJson(text) {
try {
var urls = JSON.parse(text);
var origins = {};
for (var i = 0, url; url = urls[i]; i++) {
var origin = getOriginFromUrl(url);
if (origin) {
origins[origin] = origin;
}
}
return Object.keys(origins);
} catch (e) {
console.log(UTIL_fmt('could not parse ' + text));
return [];
}
}
/**
* Retrieves a set of distinct app ids from the sign challenges.
* @param {Array.<SignChallenge>=} signChallenges Input sign challenges.
* @return {Array.<string>} array of distinct app ids.
*/
function getDistinctAppIds(signChallenges) {
if (!signChallenges) {
return [];
}
var appIds = {};
for (var i = 0, request; request = signChallenges[i]; i++) {
var appId = request['appId'];
if (appId) {
appIds[appId] = appId;
}
}
return Object.keys(appIds);
}
/**
* Provides an object to track checking a list of appIds.
* @param {!TextFetcher} fetcher A URL fetcher.
* @param {!Countdown} timer A timer by which to resolve all provided app ids.
* @param {string} origin The origin to check.
* @param {!Array.<string>} appIds The app ids to check.
* @param {boolean} allowHttp Whether to allow http:// URLs.
* @param {string=} opt_logMsgUrl A log message URL.
* @constructor
*/
function AppIdChecker(fetcher, timer, origin, appIds, allowHttp, opt_logMsgUrl)
{
/** @private {!TextFetcher} */
this.fetcher_ = fetcher;
/** @private {!Countdown} */
this.timer_ = timer;
/** @private {string} */
this.origin_ = origin;
var appIdsMap = {};
if (appIds) {
for (var i = 0; i < appIds.length; i++) {
appIdsMap[appIds[i]] = appIds[i];
}
}
/** @private {Array.<string>} */
this.distinctAppIds_ = Object.keys(appIdsMap);
/** @private {boolean} */
this.allowHttp_ = allowHttp;
/** @private {string|undefined} */
this.logMsgUrl_ = opt_logMsgUrl;
/** @private {boolean} */
this.closed_ = false;
/** @private {boolean} */
this.anyInvalidAppIds_ = false;
/** @private {number} */
this.fetchedAppIds_ = 0;
}
/**
* Checks whether all the app ids provided can be asserted by the given origin.
* @return {Promise.<boolean>} A promise for the result of the check
*/
AppIdChecker.prototype.doCheck = function() {
if (!this.distinctAppIds_.length)
return Promise.resolve(false);
if (this.allAppIdsEqualOrigin_()) {
// Trivially allowed.
return Promise.resolve(true);
} else {
var self = this;
// Begin checking remaining app ids.
var appIdChecks = self.distinctAppIds_.map(self.checkAppId_.bind(self));
return Promise.all(appIdChecks).then(function(results) {
return results.every(function(result) {
if (!result)
self.anyInvalidAppIds_ = true;
return result;
});
});
}
};
/**
* Checks if a single appId can be asserted by the given origin.
* @param {string} appId The appId to check
* @return {Promise.<boolean>} A promise for the result of the check
* @private
*/
AppIdChecker.prototype.checkAppId_ = function(appId) {
if (appId == this.origin_) {
// Trivially allowed
return Promise.resolve(true);
}
var p = this.fetchAllowedOriginsForAppId_(appId);
var self = this;
return p.then(function(allowedOrigins) {
if (allowedOrigins.indexOf(self.origin_) == -1) {
console.warn(UTIL_fmt('Origin ' + self.origin_ +
' not allowed by app id ' + appId));
return false;
}
return true;
});
};
/**
* Closes this checker. No callback will be called after this checker is closed.
*/
AppIdChecker.prototype.close = function() {
this.closed_ = true;
};
/**
* @return {boolean} Whether all the app ids being checked are equal to the
* calling origin.
* @private
*/
AppIdChecker.prototype.allAppIdsEqualOrigin_ = function() {
var self = this;
return this.distinctAppIds_.every(function(appId) {
return appId == self.origin_;
});
};
/**
* Fetches the allowed origins for an appId.
* @param {string} appId Application id
* @return {Promise.<!Array.<string>>} A promise for a list of allowed origins
* for appId
* @private
*/
AppIdChecker.prototype.fetchAllowedOriginsForAppId_ = function(appId) {
if (!appId) {
return Promise.resolve([]);
}
if (appId.indexOf('http://') == 0 && !this.allowHttp_) {
console.log(UTIL_fmt('http app ids disallowed, ' + appId + ' requested'));
return Promise.resolve([]);
}
var origin = getOriginFromUrl(appId);
if (!origin) {
return Promise.resolve([]);
}
var p = this.fetcher_.fetch(appId);
var self = this;
return p.then(getOriginsFromJson, function(rc_) {
var rc = /** @type {number} */(rc_);
console.log(UTIL_fmt('fetching ' + appId + ' failed: ' + rc));
if (!(rc >= 400 && rc < 500) && !self.timer_.expired()) {
// Retry
return self.fetchAllowedOriginsForAppId_(appId);
}
return [];
});
};