OLD | NEW |
| (Empty) |
1 // Copyright 2013 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. | |
4 | |
5 /** | |
6 * @fileoverview | |
7 * OAuth2 API flow implementations. | |
8 */ | |
9 | |
10 /** @suppress {duplicate} */ | |
11 var remoting = remoting || {}; | |
12 | |
13 (function() { | |
14 | |
15 'use strict'; | |
16 | |
17 /** | |
18 * @constructor | |
19 * @implements {remoting.OAuth2Api} | |
20 */ | |
21 remoting.OAuth2ApiImpl = function() { | |
22 }; | |
23 | |
24 /** @private | |
25 * @return {string} OAuth2 token URL. | |
26 */ | |
27 remoting.OAuth2ApiImpl.prototype.getOAuth2TokenEndpoint_ = function() { | |
28 return remoting.settings.OAUTH2_BASE_URL + '/token'; | |
29 }; | |
30 | |
31 /** @private | |
32 * @return {string} OAuth2 userinfo API URL. | |
33 */ | |
34 remoting.OAuth2ApiImpl.prototype.getOAuth2ApiUserInfoEndpoint_ = function() { | |
35 return remoting.settings.OAUTH2_API_BASE_URL + '/v1/userinfo'; | |
36 }; | |
37 | |
38 | |
39 /** | |
40 * Interprets HTTP error responses in authentication XMLHttpRequests. | |
41 * | |
42 * @private | |
43 * @param {number} xhrStatus Status (HTTP response code) of the XMLHttpRequest. | |
44 * @return {!remoting.Error} An error code to be raised. | |
45 */ | |
46 remoting.OAuth2ApiImpl.prototype.interpretXhrStatus_ = | |
47 function(xhrStatus) { | |
48 // Return AUTHENTICATION_FAILED by default, so that the user can try to | |
49 // recover from an unexpected failure by signing in again. | |
50 /** @type {!remoting.Error} */ | |
51 var error = new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED); | |
52 if (xhrStatus == 400 || xhrStatus == 401 || xhrStatus == 403) { | |
53 error = new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED); | |
54 } else if (xhrStatus == 502 || xhrStatus == 503) { | |
55 error = new remoting.Error(remoting.Error.Tag.SERVICE_UNAVAILABLE); | |
56 } else if (xhrStatus == 0) { | |
57 error = new remoting.Error(remoting.Error.Tag.NETWORK_FAILURE); | |
58 } else { | |
59 console.warn('Unexpected authentication response code: ' + xhrStatus); | |
60 } | |
61 return error; | |
62 }; | |
63 | |
64 /** | |
65 * Asynchronously retrieves a new access token from the server. | |
66 * | |
67 * @param {function(string, number): void} onDone Callback to invoke when | |
68 * the access token and expiration time are successfully fetched. | |
69 * @param {function(!remoting.Error):void} onError Callback invoked if an | |
70 * error occurs. | |
71 * @param {string} clientId OAuth2 client ID. | |
72 * @param {string} clientSecret OAuth2 client secret. | |
73 * @param {string} refreshToken OAuth2 refresh token to be redeemed. | |
74 * @return {void} Nothing. | |
75 */ | |
76 remoting.OAuth2ApiImpl.prototype.refreshAccessToken = function( | |
77 onDone, onError, clientId, clientSecret, refreshToken) { | |
78 /** @param {!remoting.Xhr.Response} response */ | |
79 var onResponse = function(response) { | |
80 if (response.status == 200) { | |
81 try { | |
82 // Don't use base.jsonParseSafe here unless you also include base.js, | |
83 // otherwise this won't work from the OAuth trampoline. | |
84 // TODO(jamiewalch): Fix this once we're no longer using the trampoline. | |
85 var tokens = JSON.parse(response.getText()); | |
86 onDone(tokens['access_token'], tokens['expires_in']); | |
87 } catch (/** @type {Error} */ err) { | |
88 console.error('Invalid "token" response from server:', err); | |
89 onError(remoting.Error.unexpected()); | |
90 } | |
91 } else { | |
92 console.error('Failed to refresh token. Status: ' + response.status + | |
93 ' response: ' + response.getText()); | |
94 onError(remoting.Error.fromHttpStatus(response.status)); | |
95 } | |
96 }; | |
97 | |
98 new remoting.Xhr({ | |
99 method: 'POST', | |
100 url: this.getOAuth2TokenEndpoint_(), | |
101 formContent: { | |
102 'client_id': clientId, | |
103 'client_secret': clientSecret, | |
104 'refresh_token': refreshToken, | |
105 'grant_type': 'refresh_token' | |
106 } | |
107 }).start().then(onResponse); | |
108 }; | |
109 | |
110 /** | |
111 * Asynchronously exchanges an authorization code for access and refresh tokens. | |
112 * | |
113 * @param {function(string, string, number): void} onDone Callback to | |
114 * invoke when the refresh token, access token and access token expiration | |
115 * time are successfully fetched. | |
116 * @param {function(!remoting.Error):void} onError Callback invoked if an | |
117 * error occurs. | |
118 * @param {string} clientId OAuth2 client ID. | |
119 * @param {string} clientSecret OAuth2 client secret. | |
120 * @param {string} code OAuth2 authorization code. | |
121 * @param {string} redirectUri Redirect URI used to obtain this code. | |
122 * @return {void} Nothing. | |
123 */ | |
124 remoting.OAuth2ApiImpl.prototype.exchangeCodeForTokens = function( | |
125 onDone, onError, clientId, clientSecret, code, redirectUri) { | |
126 /** @param {!remoting.Xhr.Response} response */ | |
127 var onResponse = function(response) { | |
128 if (response.status == 200) { | |
129 try { | |
130 // Don't use base.jsonParseSafe here unless you also include base.js, | |
131 // otherwise this won't work from the OAuth trampoline. | |
132 // TODO(jamiewalch): Fix this once we're no longer using the trampoline. | |
133 var tokens = JSON.parse(response.getText()); | |
134 onDone(tokens['refresh_token'], | |
135 tokens['access_token'], tokens['expires_in']); | |
136 } catch (/** @type {Error} */ err) { | |
137 console.error('Invalid "token" response from server:', err); | |
138 onError(remoting.Error.unexpected()); | |
139 } | |
140 } else { | |
141 console.error('Failed to exchange code for token. Status: ' + | |
142 response.status + ' response: ' + response.getText()); | |
143 onError(remoting.Error.fromHttpStatus(response.status)); | |
144 } | |
145 }; | |
146 | |
147 new remoting.Xhr({ | |
148 method: 'POST', | |
149 url: this.getOAuth2TokenEndpoint_(), | |
150 formContent: { | |
151 'client_id': clientId, | |
152 'client_secret': clientSecret, | |
153 'redirect_uri': redirectUri, | |
154 'code': code, | |
155 'grant_type': 'authorization_code' | |
156 } | |
157 }).start().then(onResponse); | |
158 }; | |
159 | |
160 /** | |
161 * Get the user's email address. | |
162 * | |
163 * @param {function(string):void} onDone Callback invoked when the email | |
164 * address is available. | |
165 * @param {function(!remoting.Error):void} onError Callback invoked if an | |
166 * error occurs. | |
167 * @param {string} token Access token. | |
168 * @return {void} Nothing. | |
169 */ | |
170 remoting.OAuth2ApiImpl.prototype.getEmail = function(onDone, onError, token) { | |
171 /** @param {!remoting.Xhr.Response} response */ | |
172 var onResponse = function(response) { | |
173 if (response.status == 200) { | |
174 try { | |
175 var result = JSON.parse(response.getText()); | |
176 onDone(result['email']); | |
177 } catch (/** @type {Error} */ err) { | |
178 console.error('Invalid "userinfo" response from server:', err); | |
179 onError(remoting.Error.unexpected()); | |
180 } | |
181 } else { | |
182 console.error('Failed to get email. Status: ' + response.status + | |
183 ' response: ' + response.getText()); | |
184 onError(remoting.Error.fromHttpStatus(response.status)); | |
185 } | |
186 }; | |
187 new remoting.Xhr({ | |
188 method: 'GET', | |
189 url: this.getOAuth2ApiUserInfoEndpoint_(), | |
190 oauthToken: token | |
191 }).start().then(onResponse); | |
192 }; | |
193 | |
194 /** | |
195 * Get the user's email address and full name. | |
196 * | |
197 * @param {function(string, string):void} onDone Callback invoked when the email | |
198 * address and full name are available. | |
199 * @param {function(!remoting.Error):void} onError Callback invoked if an | |
200 * error occurs. | |
201 * @param {string} token Access token. | |
202 * @return {void} Nothing. | |
203 */ | |
204 remoting.OAuth2ApiImpl.prototype.getUserInfo = | |
205 function(onDone, onError, token) { | |
206 /** @param {!remoting.Xhr.Response} response */ | |
207 var onResponse = function(response) { | |
208 if (response.status == 200) { | |
209 try { | |
210 var result = JSON.parse(response.getText()); | |
211 onDone(result['email'], result['name']); | |
212 } catch (/** @type {Error} */ err) { | |
213 console.error('Invalid "userinfo" response from server:', err); | |
214 onError(remoting.Error.unexpected()); | |
215 } | |
216 } else { | |
217 console.error('Failed to get user info. Status: ' + response.status + | |
218 ' response: ' + response.getText()); | |
219 onError(remoting.Error.fromHttpStatus(response.status)); | |
220 } | |
221 }; | |
222 new remoting.Xhr({ | |
223 method: 'GET', | |
224 url: this.getOAuth2ApiUserInfoEndpoint_(), | |
225 oauthToken: token | |
226 }).start().then(onResponse); | |
227 }; | |
228 | |
229 /** @returns {!remoting.Error} */ | |
230 function fromHttpStatus(/** number */ status) { | |
231 var error = remoting.Error.fromHttpStatus(status); | |
232 if (error === remoting.Error.unexpected()) { | |
233 // Return AUTHENTICATION_FAILED by default, so that the user can try to | |
234 // recover from an unexpected failure by signing in again. | |
235 return new remoting.Error(remoting.Error.Tag.AUTHENTICATION_FAILED); | |
236 } | |
237 return error; | |
238 } | |
239 | |
240 /** @type {remoting.OAuth2Api} */ | |
241 remoting.oauth2Api = new remoting.OAuth2ApiImpl(); | |
242 | |
243 })(); | |
OLD | NEW |