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