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

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: Address CL feedbacks 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.ActivityNotFoundException;
9 import android.content.ComponentName;
10 import android.content.Intent;
11 import android.content.pm.PackageManager;
12 import android.net.Uri;
13 import android.util.Base64;
14 import android.util.Log;
15
16 import java.security.SecureRandom;
17 import java.util.HashMap;
18
19 /**
20 * This class is responsible for fetching a third party token from the user usin g the OAuth2
21 * implicit flow. It pops up a third party login page located at |tokenurl|. I t relies on the
22 * |ThirdPartyTokenFetcher$OAuthRedirectActivity| to intercept the access token from the redirect at
23 * |REDIRECT_URI_SCHEME|://|REDIRECT_URI_HOST| upon successful login.
24 */
25 public class ThirdPartyTokenFetcher {
26 /** Callback for receiving the token. */
27 public interface Callback {
28 void onTokenFetched(String code, String accessToken);
29 }
30
31 /** Redirect Uri. See http://tools.ietf.org/html/rfc6749#section-3.1.2. */
Lambros 2014/06/17 02:01:39 s/Uri/URI
kelvinp 2014/06/17 03:33:40 Done.
32 private static final String REDIRECT_URI_SCHEME = "org.chromium.chromoting";
Lambros 2014/06/17 02:01:40 As mentioned before, use getPackageName() for this
kelvinp 2014/06/17 03:33:40 Done.
33
34 private static final String REDIRECT_URI_HOST = "oauthredirect";
35
36 private static final String REDIRECT_URI = REDIRECT_URI_SCHEME + "://" + RED IRECT_URI_HOST;
37
38 /**
39 * Request both the authorization code and access token from the server. Se e
40 * http://tools.ietf.org/html/rfc6749#section-3.1.1.
41 */
42 private static final String RESPONSE_TYPE = "code token";
43
44 /** This is used to securely generate an opaque 128 bit for the |mState| var iable. */
45 private static SecureRandom sSecureRandom = new SecureRandom();
46
47 /** This is used to launch the third party login page in the browser. */
48 private Activity mContext;
49
50 /**
51 * An opaque value used by the client to maintain state between the request and callback. The
52 * authorization server includes this value when redirecting the user-agent back to the client.
53 * The parameter is used for preventing cross-site request forgery. See
54 * http://tools.ietf.org/html/rfc6749#section-10.12.
55 */
56 private final String mState;
57
58 /** Url to pop the third party login page. */
Lambros 2014/06/17 02:01:40 s/Url/URL "Url" is correct for identifiers, but lo
kelvinp 2014/06/17 03:33:40 Fixed. Also fixed the pop, sorry bad English :(
59 private final String mTokenUrl;
60
61 /** The client identifier. See http://tools.ietf.org/html/rfc6749#section-2. 2. */
62 private final String mClientId;
63
64 /** The scope of access request. See http://tools.ietf.org/html/rfc6749#sect ion-3.3. */
65 private final String mScope;
66
67 private final Callback mCallback;
68
69 public ThirdPartyTokenFetcher(Activity context,
70 String tokenUrl,
71 String clientId,
72 String scope,
73 Callback callback) {
74 this.mContext = context;
75 this.mTokenUrl = tokenUrl;
76 this.mClientId = clientId;
77 this.mState = generateXsrfToken();
78 this.mScope = scope;
79 this.mCallback = callback;
80 }
81
82 public void fetchToken() {
83 Uri.Builder uriBuilder = Uri.parse(mTokenUrl).buildUpon();
84 uriBuilder.appendQueryParameter("redirect_uri", REDIRECT_URI);
85 uriBuilder.appendQueryParameter("scope", mScope);
86 uriBuilder.appendQueryParameter("client_id", mClientId);
87 uriBuilder.appendQueryParameter("state", mState);
88 uriBuilder.appendQueryParameter("response_type", RESPONSE_TYPE);
89
90 Uri uri = uriBuilder.build();
91 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
92 Log.i("ThirdPartyAuth", "fetchToken() url:" + uri);
Lambros 2014/06/17 02:01:40 I'd prefer we don't log this. We should keep infor
kelvinp 2014/06/17 03:33:40 The user is going to see the url in the browser an
93 OAuthRedirectActivity.setEnabled(mContext, true);
94
95 try {
96 mContext.startActivity(intent);
97 } catch (ActivityNotFoundException e) {
98 failFetchToken("No browser is installed to open " + uri);
Lambros 2014/06/17 02:01:40 As before, don't log |uri|.
kelvinp 2014/06/17 03:33:40 Done.
99 }
100 }
101
102 private boolean isValidIntent(Intent intent) {
103 assert intent != null;
104
105 final String action = intent.getAction();
Lambros 2014/06/17 02:01:39 Why is |action| final but not |data| ? Probably do
kelvinp 2014/06/17 03:33:40 I generally put final on variable that I don't exp
106
107 Uri data = intent.getData();
108 if (data != null) {
109 return Intent.ACTION_VIEW.equals(action) &&
110 REDIRECT_URI_SCHEME.equals(data.getScheme()) &&
111 REDIRECT_URI_HOST.equals(data.getHost());
112 }
113 return false;
114 }
115
116 public boolean handleTokenFetched(Intent intent) {
117 assert intent != null;
118
119 if (!isValidIntent(intent)) {
120 Log.w("ThirdPartyAuth", "Ignoring unmatched intent.");
121 return false;
122 }
123
124 Uri data = intent.getData();
125 HashMap<String, String> params = getFragmentParameters(data);
Lambros 2014/06/17 02:01:39 Use Uri.getQueryParameter() instead, then you don'
kelvinp 2014/06/17 03:33:40 According to the OAuth Standard (http://openid.net
126
127 final String accessToken = params.get("access_token");
Lambros 2014/06/17 02:01:39 Is "final" useful here?
kelvinp 2014/06/17 03:33:40 Done.
128 final String code = params.get("code");
129 final String state = params.get("state");
130
131 if (!mState.equals(state)) {
132 failFetchToken("Ignoring redirect with invalid state.");
133 return false;
134 }
135
136 if (code == null || accessToken == null) {
137 failFetchToken("Ignoring redirect with missing code or token.");
138 return false;
139 }
140
141 Log.i("ThirdPartyAuth", "handleTokenFetched().");
142 mCallback.onTokenFetched(code, accessToken);
143 OAuthRedirectActivity.setEnabled(mContext, false);
144 return true;
145 }
146
147 private void failFetchToken(String errorMessage) {
148 Log.e("ThirdPartyAuth", errorMessage);
149 mCallback.onTokenFetched("", "");
150 OAuthRedirectActivity.setEnabled(mContext, false);
151 }
152
153 /** Generate a 128 bit URL-safe opaque string to prevent cross site request forgery (XSRF).*/
154 private static String generateXsrfToken() {
155 byte[] bytes = new byte[16];
156 sSecureRandom.nextBytes(bytes);
157 // Uses a variant of Base64 to make sure the URL is URL safe:
158 // URL_SAFE replaces - with _ and + with /.
159 // NO_WRAP removes the trailing newline character.
160 // NO_PADDING removes any trailing =.
161 return Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP | B ase64.NO_PADDING);
162 }
163
164 /** Parses the fragment string into a key value pair. */
165 private static HashMap<String, String> getFragmentParameters(Uri uri) {
166 assert uri != null;
167 HashMap<String, String> result = new HashMap<String, String>();
168
169 final String fragment = uri.getFragment();
170
171 if (fragment != null) {
172 String[] parts = fragment.split("&");
173
174 for (String part : parts) {
175 String keyValuePair[] = part.split("=", 2);
176 if (keyValuePair.length == 2) {
177 result.put(keyValuePair[0], keyValuePair[1]);
178 }
179 }
180 }
181 return result;
182 };
183
184 /**
185 * In the OAuth2 implicit flow, the browser will be redirected to
186 * |REDIRECT_URI_SCHEME|://|REDIRECT_URI_HOST| upon a successful login. OAut hRedirectActivity
187 * uses an intent filter in the manifest to intercept the URL and launch the chromoting app.
188 *
189 * Unfortunately, Most browsers on Android, e.g. chrome, reloads the URL whe n a browser
Lambros 2014/06/17 02:01:39 s/Most/most s/reloads/reload
kelvinp 2014/06/17 03:33:40 Done.
190 * tab is activated. As a result, chromoting is launched unintentionally wh en the user restarts
191 * chrome or closes other tabs that causes the |redirect URL| to become the topmost tab.
Lambros 2014/06/17 02:01:39 Remove '|'s. These are for quoting Java identifier
kelvinp 2014/06/17 03:33:40 Done.
192 *
193 * To solve the problem, the redirect intent-filter is declared in a separat e activity,
194 * |OAuthRedirectActivity| instead of the MainActivity. In this way, we can disable it,
195 * together with its intent filter, by default. |OAuthRedirectActivity| is o nly enabled when
196 * there is a pending token fetch request.
197 */
198 public static class OAuthRedirectActivity extends Activity {
Lambros 2014/06/17 02:01:40 I think I'd prefer this as a top level class, thou
kelvinp 2014/06/17 03:33:40 I made this an inner class as this class is concep
199 @Override
200 public void onStart() {
201 super.onStart();
202 // |OAuthRedirectActivity| runs in its own task, it needs to route t he intent back
203 // to Chromoting.java to access the state of the current request.
204 Intent intent = getIntent();
205 intent.setClass(this, Chromoting.class);
206 startActivity(intent);
207 finishActivity(0);
208 }
209
210 public static void setEnabled(Activity context, boolean enabled) {
211 int state = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
212 : PackageManager.COMPONENT_ENABLED_STATE_DEFAULT ;
213 context.getPackageManager().setComponentEnabledSetting(
214 new ComponentName(
215 context.getApplicationContext(),
216 ThirdPartyTokenFetcher.OAuthRedirectActivity.class),
217 state,
Lambros 2014/06/17 02:01:39 Indentation.
kelvinp 2014/06/17 03:33:40 My Indentation is generated by clang format. Are
Lambros 2014/06/17 23:13:48 Actually I hadn't noticed that - I thought that |s
218 PackageManager.DONT_KILL_APP);
219 }
220 }
221 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698