[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / resources / cryptotoken / webrequest.js
blob3dd5a0a8848001968a72a43c21f521d369d3626c
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 Does common handling for requests coming from web pages and
7 * routes them to the provided handler.
8 */
10 /**
11 * Gets the scheme + origin from a web url.
12 * @param {string} url Input url
13 * @return {?string} Scheme and origin part if url parses
15 function getOriginFromUrl(url) {
16 var re = new RegExp('^(https?://)[^/]*/?');
17 var originarray = re.exec(url);
18 if (originarray == null) return originarray;
19 var origin = originarray[0];
20 while (origin.charAt(origin.length - 1) == '/') {
21 origin = origin.substring(0, origin.length - 1);
23 if (origin == 'http:' || origin == 'https:')
24 return null;
25 return origin;
28 /**
29 * Parses the text as JSON and returns it as an array of strings.
30 * @param {string} text Input JSON
31 * @return {Array.<string>} Array of origins
33 function getOriginsFromJson(text) {
34 try {
35 var urls = JSON.parse(text);
36 var origins = [];
37 for (var i = 0, url; url = urls[i]; i++) {
38 var origin = getOriginFromUrl(url);
39 if (origin)
40 origins.push(origin);
42 return origins;
43 } catch (e) {
44 console.log(UTIL_fmt('could not parse ' + text));
45 return [];
49 /**
50 * Fetches the app id, and calls a callback with list of allowed origins for it.
51 * @param {string} appId the app id to fetch.
52 * @param {Function} cb called with a list of allowed origins for the app id.
54 function fetchAppId(appId, cb) {
55 var origin = getOriginFromUrl(appId);
56 if (!origin) {
57 cb(404, appId);
58 return;
60 var xhr = new XMLHttpRequest();
61 var origins = [];
62 xhr.open('GET', appId, true);
63 xhr.onloadend = function() {
64 if (xhr.status != 200) {
65 cb(xhr.status, appId);
66 return;
68 cb(xhr.status, appId, getOriginsFromJson(xhr.responseText));
70 xhr.send();
73 /**
74 * Retrieves a set of distinct app ids from the SignData.
75 * @param {SignData=} signData Input signature data
76 * @return {Array.<string>} array of distinct app ids.
78 function getDistinctAppIds(signData) {
79 var appIds = [];
80 if (!signData) {
81 return appIds;
83 for (var i = 0, request; request = signData[i]; i++) {
84 var appId = request['appId'];
85 if (appId && appIds.indexOf(appId) == -1) {
86 appIds.push(appId);
89 return appIds;
92 /**
93 * Reorganizes the requests from the SignData to an array of
94 * (appId, [Request]) tuples.
95 * @param {SignData} signData Input signature data
96 * @return {Array.<[string, Array.<Request>]>} array of
97 * (appId, [Request]) tuples.
99 function requestsByAppId(signData) {
100 var requests = {};
101 var appIdOrder = {};
102 var orderToAppId = {};
103 var lastOrder = 0;
104 for (var i = 0, request; request = signData[i]; i++) {
105 var appId = request['appId'];
106 if (appId) {
107 if (!appIdOrder.hasOwnProperty(appId)) {
108 appIdOrder[appId] = lastOrder;
109 orderToAppId[lastOrder] = appId;
110 lastOrder++;
112 if (requests[appId]) {
113 requests[appId].push(request);
114 } else {
115 requests[appId] = [request];
119 var orderedRequests = [];
120 for (var order = 0; order < lastOrder; order++) {
121 appId = orderToAppId[order];
122 orderedRequests.push([appId, requests[appId]]);
124 return orderedRequests;
128 * Fetches the allowed origins for an appId.
129 * @param {string} appId Application id
130 * @param {boolean} allowHttp Whether http is a valid scheme for an appId.
131 * (This should be false except on test domains.)
132 * @param {function(number, !Array.<string>)} cb Called back with an HTTP
133 * response code and a list of allowed origins for appId.
135 function fetchAllowedOriginsForAppId(appId, allowHttp, cb) {
136 var allowedOrigins = [];
137 if (!appId) {
138 cb(200, allowedOrigins);
139 return;
141 if (appId.indexOf('http://') == 0 && !allowHttp) {
142 console.log(UTIL_fmt('http app ids disallowed, ' + appId + ' requested'));
143 cb(200, allowedOrigins);
144 return;
146 // TODO: hack for old enrolled gnubbies, don't treat
147 // accounts.google.com/login.corp.google.com specially when cryptauth server
148 // stops reporting them as appId.
149 if (appId == 'https://accounts.google.com') {
150 allowedOrigins = ['https://login.corp.google.com'];
151 cb(200, allowedOrigins);
152 return;
154 if (appId == 'https://login.corp.google.com') {
155 allowedOrigins = ['https://accounts.google.com'];
156 cb(200, allowedOrigins);
157 return;
159 // Termination of this function relies in fetchAppId completing.
160 // (Not completing would be a bug in XMLHttpRequest.)
161 // TODO: provide a termination guarantee, e.g. with a timer?
162 fetchAppId(appId, function(rc, fetchedAppId, origins) {
163 if (rc != 200) {
164 console.log(UTIL_fmt('fetching ' + fetchedAppId + ' failed: ' + rc));
165 allowedOrigins = [];
166 } else {
167 allowedOrigins = origins;
169 cb(rc, allowedOrigins);
174 * Checks whether an appId is valid for a given origin.
175 * @param {!string} appId Application id
176 * @param {!string} origin Origin
177 * @param {!Array.<string>} allowedOrigins the list of allowed origins for each
178 * appId.
179 * @return {boolean} whether the appId is allowed for the origin.
181 function isValidAppIdForOrigin(appId, origin, allowedOrigins) {
182 if (!appId)
183 return false;
184 if (appId == origin) {
185 // trivially allowed
186 return true;
188 if (!allowedOrigins)
189 return false;
190 return allowedOrigins.indexOf(origin) >= 0;
194 * Returns whether the signData object appears to be valid.
195 * @param {Array.<Object>} signData the signData object.
196 * @return {boolean} whether the object appears valid.
198 function isValidSignData(signData) {
199 for (var i = 0; i < signData.length; i++) {
200 var incomingChallenge = signData[i];
201 if (!incomingChallenge.hasOwnProperty('challenge'))
202 return false;
203 if (!incomingChallenge.hasOwnProperty('appId')) {
204 return false;
206 if (!incomingChallenge.hasOwnProperty('keyHandle'))
207 return false;
208 if (incomingChallenge['version']) {
209 if (incomingChallenge['version'] != 'U2F_V1' &&
210 incomingChallenge['version'] != 'U2F_V2') {
211 return false;
215 return true;
218 /** Posts the log message to the log url.
219 * @param {string} logMsg the log message to post.
220 * @param {string=} opt_logMsgUrl the url to post log messages to.
222 function logMessage(logMsg, opt_logMsgUrl) {
223 console.log(UTIL_fmt('logMessage("' + logMsg + '")'));
225 if (!opt_logMsgUrl) {
226 return;
228 // Image fetching is not allowed per packaged app CSP.
229 // But video and audio is.
230 var audio = new Audio();
231 audio.src = opt_logMsgUrl + logMsg;
235 * Logs the result of fetching an appId.
236 * @param {!string} appId Application Id
237 * @param {number} millis elapsed time while fetching the appId.
238 * @param {Array.<string>} allowedOrigins the allowed origins retrieved.
239 * @param {string=} opt_logMsgUrl the url to post log messages to.
241 function logFetchAppIdResult(appId, millis, allowedOrigins, opt_logMsgUrl) {
242 var logMsg = 'log=fetchappid&appid=' + appId + '&millis=' + millis +
243 '&numorigins=' + allowedOrigins.length;
244 logMessage(logMsg, opt_logMsgUrl);
248 * Logs a mismatch between an origin and an appId.
249 * @param {string} origin Origin
250 * @param {!string} appId Application id
251 * @param {string=} opt_logMsgUrl the url to post log messages to
253 function logInvalidOriginForAppId(origin, appId, opt_logMsgUrl) {
254 var logMsg = 'log=originrejected&origin=' + origin + '&appid=' + appId;
255 logMessage(logMsg, opt_logMsgUrl);
259 * Formats response parameters as an object.
260 * @param {string} type type of the post message.
261 * @param {number} code status code of the operation.
262 * @param {Object=} responseData the response data of the operation.
263 * @return {Object} formatted response.
265 function formatWebPageResponse(type, code, responseData) {
266 var responseJsonObject = {};
267 responseJsonObject['type'] = type;
268 responseJsonObject['code'] = code;
269 if (responseData)
270 responseJsonObject['responseData'] = responseData;
271 return responseJsonObject;
275 * @param {!string} string Input string
276 * @return {Array.<number>} SHA256 hash value of string.
278 function sha256HashOfString(string) {
279 var s = new SHA256();
280 s.update(UTIL_StringToBytes(string));
281 return s.digest();
285 * Normalizes the TLS channel ID value:
286 * 1. Converts semantically empty values (undefined, null, 0) to the empty
287 * string.
288 * 2. Converts valid JSON strings to a JS object.
289 * 3. Otherwise, returns the input value unmodified.
290 * @param {Object|string|undefined} opt_tlsChannelId TLS Channel id
291 * @return {Object|string} The normalized TLS channel ID value.
293 function tlsChannelIdValue(opt_tlsChannelId) {
294 if (!opt_tlsChannelId) {
295 // Case 1: Always set some value for TLS channel ID, even if it's the empty
296 // string: this browser definitely supports them.
297 return '';
299 if (typeof opt_tlsChannelId === 'string') {
300 try {
301 var obj = JSON.parse(opt_tlsChannelId);
302 if (!obj) {
303 // Case 1: The string value 'null' parses as the Javascript object null,
304 // so return an empty string: the browser definitely supports TLS
305 // channel id.
306 return '';
308 // Case 2: return the value as a JS object.
309 return /** @type {Object} */ (obj);
310 } catch (e) {
311 console.warn('Unparseable TLS channel ID value ' + opt_tlsChannelId);
312 // Case 3: return the value unmodified.
315 return opt_tlsChannelId;
319 * Creates a browser data object with the given values.
320 * @param {!string} type A string representing the "type" of this browser data
321 * object.
322 * @param {!string} serverChallenge The server's challenge, as a base64-
323 * encoded string.
324 * @param {!string} origin The server's origin, as seen by the browser.
325 * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
326 * @return {string} A string representation of the browser data object.
328 function makeBrowserData(type, serverChallenge, origin, opt_tlsChannelId) {
329 var browserData = {
330 'typ' : type,
331 'challenge' : serverChallenge,
332 'origin' : origin
334 browserData['cid_pubkey'] = tlsChannelIdValue(opt_tlsChannelId);
335 return JSON.stringify(browserData);
339 * Creates a browser data object for an enroll request with the given values.
340 * @param {!string} serverChallenge The server's challenge, as a base64-
341 * encoded string.
342 * @param {!string} origin The server's origin, as seen by the browser.
343 * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
344 * @return {string} A string representation of the browser data object.
346 function makeEnrollBrowserData(serverChallenge, origin, opt_tlsChannelId) {
347 return makeBrowserData(
348 'navigator.id.finishEnrollment', serverChallenge, origin,
349 opt_tlsChannelId);
353 * Creates a browser data object for a sign request with the given values.
354 * @param {!string} serverChallenge The server's challenge, as a base64-
355 * encoded string.
356 * @param {!string} origin The server's origin, as seen by the browser.
357 * @param {Object|string|undefined} opt_tlsChannelId TLS Channel Id
358 * @return {string} A string representation of the browser data object.
360 function makeSignBrowserData(serverChallenge, origin, opt_tlsChannelId) {
361 return makeBrowserData(
362 'navigator.id.getAssertion', serverChallenge, origin, opt_tlsChannelId);
366 * @param {string} browserData Browser data as JSON
367 * @param {string} appId Application Id
368 * @param {string} encodedKeyHandle B64 encoded key handle
369 * @param {string=} version Protocol version
370 * @return {SignHelperChallenge} Challenge object
372 function makeChallenge(browserData, appId, encodedKeyHandle, version) {
373 var appIdHash = B64_encode(sha256HashOfString(appId));
374 var browserDataHash = B64_encode(sha256HashOfString(browserData));
375 var keyHandle = encodedKeyHandle;
377 var challenge = {
378 'challengeHash': browserDataHash,
379 'appIdHash': appIdHash,
380 'keyHandle': keyHandle
382 // Version is implicitly U2F_V1 if not specified.
383 challenge['version'] = (version || 'U2F_V1');
384 return challenge;