Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(556)

Side by Side Diff: remoting/android/java/src/org/chromium/chromoting/ThirdPartyTokenFetcher.java

Issue 337013002: Third Party Authentication for Android Part III - Android OAuth2 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 package org.chromium.chromoting;
6
7 import android.app.Activity;
8 import android.content.ComponentName;
9 import android.content.Intent;
10 import android.content.pm.PackageManager;
11 import android.net.Uri;
12 import android.util.Base64;
13 import android.util.Log;
14
15 import java.security.SecureRandom;
16 import java.util.HashMap;
17
18 /**
19 * This class is responsible for fetching a third party token from the user usin g the OAuth2
20 * implicit flow. It pops up a third party login page located at |tokenurl|. I t relies on the
21 * |ThirdPartyTokenFetcher$OAuthRedirectActivity| to intercept the access token from the redirect at
22 * |REDIRECT_URI_SCHEME|://|REDIRECT_URI_HOST| upon successful login.
23 */
24 public class ThirdPartyTokenFetcher {
25 /** Callback for receiving the token. */
26 public interface Callback {
27 void onTokenFetched(String code, String accessToken);
28 }
29
30 /** Redirect Uri. See http://tools.ietf.org/html/rfc6749#section-3.1.2. */
31 private static final String REDIRECT_URI_SCHEME = "chromoting";
32
33 private static final String REDIRECT_URI_HOST = "oauthredirect";
34
35 private static final String REDIRECT_URI = REDIRECT_URI_SCHEME + "://" + RED IRECT_URI_HOST;
36
37 /**
38 * Request both the authorization code and access token from the server. Se e
39 * http://tools.ietf.org/html/rfc6749#section-3.1.1.
40 */
41 private static final String RESPONSE_TYPE = "code token";
42
43 /** This is used to securely generate an opaque 128 bit for the |mState| var iable. */
44 private static SecureRandom sSecureRandom = new SecureRandom();
45
46 /** This is used to launch the third party login page in the browser. */
47 private Activity mContext;
48
49 /**
50 * An opaque value used by the client to maintain state between the request and callback. The
51 * authorization server includes this value when redirecting the user-agent back to the client.
52 * The parameter is used for preventing cross-site request forgery. See
53 * http://tools.ietf.org/html/rfc6749#section-10.12.
54 */
55 private final String mState;
56
57 /** Url to pop the third party login page. */
58 private final String mTokenUrl;
59
60 /** The client identifier. See http://tools.ietf.org/html/rfc6749#section-2. 2. */
61 private final String mClientId;
62
63 /** The scope of access request. See http://tools.ietf.org/html/rfc6749#sect ion-3.3. */
64 private final String mScope;
65
66 private final Callback mCallback;
67
68 public ThirdPartyTokenFetcher(Activity context,
69 String tokenUrl,
70 String clientId,
71 String scope,
72 Callback callback) {
73 this.mContext = context;
74 this.mTokenUrl = tokenUrl;
75 this.mClientId = clientId;
76 this.mState = generateXsrfToken();
77 this.mScope = scope;
78 this.mCallback = callback;
79 }
80
81 public void fetchToken() {
82 Uri.Builder uriBuilder = Uri.parse(mTokenUrl).buildUpon();
83 uriBuilder.appendQueryParameter("redirect_uri", REDIRECT_URI);
84 uriBuilder.appendQueryParameter("scope", mScope);
85 uriBuilder.appendQueryParameter("client_id", mClientId);
86 uriBuilder.appendQueryParameter("state", mState);
87 uriBuilder.appendQueryParameter("response_type", RESPONSE_TYPE);
88
89 Uri uri = uriBuilder.build();
90 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
Sergey Ulanov 2014/06/16 22:55:38 Does this pop out external browser? If yes, then I
kelvinp 2014/06/17 00:02:57 Spoke offline with Sergey and Renato. We are usin
91
92 // Verify that the device can launch an application for this intent, oth erwise
93 // startActivity() may crash the application.
Sergey Ulanov 2014/06/16 22:55:38 Can we just catch exception thrown by startActivit
kelvinp 2014/06/17 00:02:56 Good point.
94 if (null == intent.resolveActivity(mContext.getPackageManager())) {
95 Log.e("ThirdPartyAuth", "No browser is installed to open " + uri);
96 this.failFetchToken();
97 return;
98 }
99 Log.i("ThirdPartyAuth", "fetchToken() url:" + uri);
100 OAuthRedirectActivity.setEnabled(mContext, true);
101 mContext.startActivity(intent);
102 }
103
104 private boolean isValidIntent(Intent intent) {
105 assert intent != null;
106
107 final String action = intent.getAction();
108
109 Uri data = intent.getData();
110 if (data != null) {
111 String scheme = data.getScheme();
112 String host = data.getHost();
Sergey Ulanov 2014/06/16 22:55:38 I think you want getAuthority here, to also verify
kelvinp 2014/06/17 00:02:56 Done for the extra variables. I kept the call to g
113 return Intent.ACTION_VIEW.equals(action) &&
114 REDIRECT_URI_SCHEME.equals(scheme) &&
115 REDIRECT_URI_HOST.equals(host);
116 }
117 return false;
118 }
119
120 public boolean handleTokenFetched(Intent intent) {
121 assert intent != null;
122
123 if (!isValidIntent(intent)) {
124 Log.w("ThirdPartyAuth", "Ignoring unmatched intent.");
125 return false;
126 }
127
128 Uri data = intent.getData();
129 HashMap<String, String> params = getFragmentParameters(data);
130
131 final String accessToken = params.get("access_token");
132 final String code = params.get("code");
133 final String state = params.get("state");
134
135 if (!mState.equals(state)) {
136 Log.e("ThirdPartyAuth", "Ignoring redirect with invalid state.");
137 return false;
138 }
139
140 if (code == null || accessToken == null) {
141 Log.e("ThirdPartyAuth", "Ignoring redirect with missing code or toke n.");
142 return false;
rmsousa 2014/06/16 22:13:15 This would ignore explicit errors (where the error
kelvinp 2014/06/17 00:02:56 Done.
143 }
144
145 Log.i("ThirdPartyAuth", "handleTokenFetched().");
146 mCallback.onTokenFetched(code, accessToken);
147 OAuthRedirectActivity.setEnabled(mContext, false);
148 return true;
149 }
150
151 private void failFetchToken() {
152 mCallback.onTokenFetched("", "");
153 }
154
155 /** Generate a 128 bit URL-safe opaque string to prevent cross site request forgery (XSRF).*/
156 private static String generateXsrfToken() {
157 byte[] bytes = new byte[16];
158 sSecureRandom.nextBytes(bytes);
159 // Uses a variant of Base64 to make sure the URL is URL safe:
160 // URL_SAFE replaces - with _ and + with /.
161 // NO_WRAP removes the trailing newline character.
162 // NO_PADDING removes any trailing =.
163 return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP | B ase64.NO_PADDING);
164 }
165
166 /** Parses the fragment string into a key value pair. */
167 private static HashMap<String, String> getFragmentParameters(Uri uri) {
168 assert uri != null;
169 HashMap<String, String> result = new HashMap<String, String>();
170
171 final String fragment = uri.getFragment();
172
173 if (fragment != null) {
174 String[] parts = fragment.split("&");
175
176 for (String part : parts) {
177 String keyValuePair[] = part.split("=", 2);
178 if (keyValuePair.length == 2) {
179 result.put(keyValuePair[0], keyValuePair[1]);
180 }
181 }
182 }
183 return result;
184 };
185
186 /**
187 * In the OAuth2 implicit flow, the browser will be redirected to
188 * |REDIRECT_URI_SCHEME|://|REDIRECT_URI_HOST| upon a successful login. OAut hRedirectActivity
189 * uses an intent filter in the manifest to intercept the URL and launch the chromoting app.
190 *
191 * Unfortunately, Most browsers on Android, e.g. chrome, reloads the URL whe n a browser
192 * tab is activated. As a result, chromoting is launched unintentionally wh en the user restarts
193 * chrome or closes other tabs that causes the |redirect URL| to become the topmost tab.
194 *
195 * To solve the problem, the redirect intent-filter is declared in a separat e activity,
196 * |OAuthRedirectActivity| instead of the MainActivity. In this way, we can disable it,
197 * together with its intent filter, by default. |OAuthRedirectActivity| is o nly enabled when
198 * there is a pending token fetch request.
199 */
200 public static class OAuthRedirectActivity extends Activity {
201 @Override
202 public void onStart() {
203 super.onStart();
204 // |OAuthRedirectActivity| runs in its own task, it needs to route t he intent back
205 // to Chromoting.java to access the state of the current request.
206 Intent intent = getIntent();
207 intent.setClass(this, Chromoting.class);
208 startActivity(intent);
209 finishActivity(0);
210 }
211
212 public static void setEnabled(Activity context, boolean enabled) {
213 int state = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
214 : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT ;
215 context.getPackageManager().setComponentEnabledSetting(
216 new ComponentName(
217 context.getApplicationContext(),
218 ThirdPartyTokenFetcher.OAuthRedirectActivity.class),
219 state,
220 PackageManager.DONT_KILL_APP);
221 }
222 }
223 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698