Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / cryptotoken / appid.js
blob1424ed0ac4a09342a996d8616cf03056a6fa9581
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview Implements a check whether an app id lists an origin.
7  */
8 'use strict';
10 /**
11  * Parses the text as JSON and returns it as an array of strings.
12  * @param {string} text Input JSON
13  * @return {!Array<string>} Array of origins
14  */
15 function getOriginsFromJson(text) {
16   try {
17     var urls, i;
18     var appIdData = JSON.parse(text);
19     if (Array.isArray(appIdData)) {
20       // Older format where it is a simple list of facets
21       urls = appIdData;
22     } else {
23       var trustedFacets = appIdData['trustedFacets'];
24       if (trustedFacets) {
25         var versionBlock;
26         for (i = 0; versionBlock = trustedFacets[i]; i++) {
27           if (versionBlock['version'] &&
28               versionBlock['version']['major'] == 1 &&
29               versionBlock['version']['minor'] == 0) {
30             urls = versionBlock['ids'];
31             break;
32           }
33         }
34       }
35       if (typeof urls == 'undefined') {
36         throw Error('Could not find trustedFacets for version 1.0');
37       }
38     }
39     var origins = {};
40     var url;
41     for (i = 0; url = urls[i]; i++) {
42       var origin = getOriginFromUrl(url);
43       if (origin) {
44         origins[origin] = origin;
45       }
46     }
47     return Object.keys(origins);
48   } catch (e) {
49     console.error(UTIL_fmt('could not parse ' + text));
50     return [];
51   }
54 /**
55  * Retrieves a set of distinct app ids from the sign challenges.
56  * @param {Array<SignChallenge>=} signChallenges Input sign challenges.
57  * @return {Array<string>} array of distinct app ids.
58  */
59 function getDistinctAppIds(signChallenges) {
60   if (!signChallenges) {
61     return [];
62   }
63   var appIds = {};
64   for (var i = 0, request; request = signChallenges[i]; i++) {
65     var appId = request['appId'];
66     if (appId) {
67       appIds[appId] = appId;
68     }
69   }
70   return Object.keys(appIds);
73 /**
74  * An object that checks one or more appIds' contents against an origin.
75  * @interface
76  */
77 function AppIdChecker() {}
79 /**
80  * Checks whether the given origin is allowed by all of the given appIds.
81  * @param {!Countdown} timer A timer by which to resolve all provided app ids.
82  * @param {string} origin The origin to check.
83  * @param {!Array<string>} appIds The app ids to check.
84  * @param {boolean} allowHttp Whether to allow http:// URLs.
85  * @param {string=} opt_logMsgUrl A log message URL.
86  * @return {Promise<boolean>} A promise for the result of the check
87  */
88 AppIdChecker.prototype.checkAppIds =
89     function(timer, origin, appIds, allowHttp, opt_logMsgUrl) {};
91 /**
92  * An interface to create an AppIdChecker.
93  * @interface
94  */
95 function AppIdCheckerFactory() {}
97 /**
98  * @return {!AppIdChecker} A new AppIdChecker.
99  */
100 AppIdCheckerFactory.prototype.create = function() {};
103  * Provides an object to track checking a list of appIds.
104  * @param {!TextFetcher} fetcher A URL fetcher.
105  * @constructor
106  * @implements AppIdChecker
107  */
108 function XhrAppIdChecker(fetcher) {
109   /** @private {!TextFetcher} */
110   this.fetcher_ = fetcher;
114  * Checks whether all the app ids provided can be asserted by the given origin.
115  * @param {!Countdown} timer A timer by which to resolve all provided app ids.
116  * @param {string} origin The origin to check.
117  * @param {!Array<string>} appIds The app ids to check.
118  * @param {boolean} allowHttp Whether to allow http:// URLs.
119  * @param {string=} opt_logMsgUrl A log message URL.
120  * @return {Promise<boolean>} A promise for the result of the check
121  */
122 XhrAppIdChecker.prototype.checkAppIds =
123     function(timer, origin, appIds, allowHttp, opt_logMsgUrl) {
124   if (this.timer_) {
125     // Can't use the same object to check appIds more than once.
126     return Promise.resolve(false);
127   }
129   /** @private {!Countdown} */
130   this.timer_ = timer;
131   /** @private {string} */
132   this.origin_ = origin;
133   var appIdsMap = {};
134   if (appIds) {
135     for (var i = 0; i < appIds.length; i++) {
136       appIdsMap[appIds[i]] = appIds[i];
137     }
138   }
139   /** @private {Array<string>} */
140   this.distinctAppIds_ = Object.keys(appIdsMap);
141   /** @private {boolean} */
142   this.allowHttp_ = allowHttp;
143   /** @private {string|undefined} */
144   this.logMsgUrl_ = opt_logMsgUrl;
145   if (!this.distinctAppIds_.length)
146     return Promise.resolve(false);
148   if (this.allAppIdsEqualOrigin_()) {
149     // Trivially allowed.
150     return Promise.resolve(true);
151   } else {
152     var self = this;
153     // Begin checking remaining app ids.
154     var appIdChecks = self.distinctAppIds_.map(self.checkAppId_.bind(self));
155     return Promise.all(appIdChecks).then(function(results) {
156       return results.every(function(result) {
157         return result;
158       });
159     });
160   }
164  * Checks if a single appId can be asserted by the given origin.
165  * @param {string} appId The appId to check
166  * @return {Promise<boolean>} A promise for the result of the check
167  * @private
168  */
169 XhrAppIdChecker.prototype.checkAppId_ = function(appId) {
170   if (appId == this.origin_) {
171     // Trivially allowed
172     return Promise.resolve(true);
173   }
174   var p = this.fetchAllowedOriginsForAppId_(appId);
175   var self = this;
176   return p.then(function(allowedOrigins) {
177     if (allowedOrigins.indexOf(self.origin_) == -1) {
178       console.warn(UTIL_fmt('Origin ' + self.origin_ +
179             ' not allowed by app id ' + appId));
180       return false;
181     }
182     return true;
183   });
187  * @return {boolean} Whether all the app ids being checked are equal to the
188  * calling origin.
189  * @private
190  */
191 XhrAppIdChecker.prototype.allAppIdsEqualOrigin_ = function() {
192   var self = this;
193   return this.distinctAppIds_.every(function(appId) {
194     return appId == self.origin_;
195   });
199  * Fetches the allowed origins for an appId.
200  * @param {string} appId Application id
201  * @return {Promise<!Array<string>>} A promise for a list of allowed origins
202  *     for appId
203  * @private
204  */
205 XhrAppIdChecker.prototype.fetchAllowedOriginsForAppId_ = function(appId) {
206   if (!appId) {
207     return Promise.resolve([]);
208   }
210   if (appId.indexOf('http://') == 0 && !this.allowHttp_) {
211     console.log(UTIL_fmt('http app ids disallowed, ' + appId + ' requested'));
212     return Promise.resolve([]);
213   }
215   var origin = getOriginFromUrl(appId);
216   if (!origin) {
217     return Promise.resolve([]);
218   }
220   var p = this.fetcher_.fetch(appId);
221   var self = this;
222   return p.then(getOriginsFromJson, function(rc_) {
223     var rc = /** @type {number} */(rc_);
224     console.log(UTIL_fmt('fetching ' + appId + ' failed: ' + rc));
225     if (!(rc >= 400 && rc < 500) && !self.timer_.expired()) {
226       // Retry
227       return self.fetchAllowedOriginsForAppId_(appId);
228     }
229     return [];
230   });
234  * A factory to create an XhrAppIdChecker.
235  * @implements AppIdCheckerFactory
236  * @param {!TextFetcher} fetcher
237  * @constructor
238  */
239 function XhrAppIdCheckerFactory(fetcher) {
240   /** @private {!TextFetcher} */
241   this.fetcher_ = fetcher;
245  * @return {!AppIdChecker} A new AppIdChecker.
246  */
247 XhrAppIdCheckerFactory.prototype.create = function() {
248   return new XhrAppIdChecker(this.fetcher_);