├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── gradle.properties
├── settings.gradle
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── microsoft
│ └── services
│ └── msa
│ ├── AccessTokenRequest.java
│ ├── AuthorizationRequest.java
│ ├── Config.java
│ ├── DefaultObservableOAuthRequest.java
│ ├── DeviceType.java
│ ├── ErrorMessages.java
│ ├── LiveAuthClient.java
│ ├── LiveAuthException.java
│ ├── LiveAuthListener.java
│ ├── LiveConnectSession.java
│ ├── LiveConnectUtils.java
│ ├── LiveOperationException.java
│ ├── LiveStatus.java
│ ├── MicrosoftOAuthConfig.java
│ ├── OAuth.java
│ ├── OAuthConfig.java
│ ├── OAuthErrorResponse.java
│ ├── OAuthRequestObserver.java
│ ├── OAuthResponse.java
│ ├── OAuthResponseVisitor.java
│ ├── OAuthSuccessfulResponse.java
│ ├── ObservableOAuthRequest.java
│ ├── OverwriteOption.java
│ ├── PreferencesConstants.java
│ ├── QueryParameters.java
│ ├── RefreshAccessTokenRequest.java
│ ├── ScreenSize.java
│ ├── TokenRequest.java
│ ├── TokenRequestAsync.java
│ └── UriBuilder.java
└── res
└── values
└── strings.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .settings/
2 | .loadpath
3 | .metadata
4 |
5 | *.keystore
6 | *.orig
7 | *.log
8 | *.tmp
9 | *.bak
10 |
11 | # built application files
12 | *.apk
13 | *.ap_
14 |
15 | # files for the dex VM
16 | *.dex
17 |
18 | # Java class files
19 | *.class
20 |
21 | # Android Studio & Gradle
22 | .idea/
23 | .gradle/
24 | local.properties
25 | /*/out
26 | /*/*/build
27 | /*/*/production
28 | *.iws
29 | *.ipr
30 | *.iml
31 | *~
32 | *.swp
33 | *.pro
34 |
35 | # generated files
36 | bin/
37 | gen/
38 | target/
39 | gen-external-apklibs/
40 |
41 | # Ignore gradle files
42 | build/
43 |
44 | # Proguard folder generated by Eclipse
45 | proguard/
46 |
47 | # Mac
48 | .DS_Store
49 |
50 | ## Gradle
51 | .gradle/
52 | gradle/
53 | gradlew
54 | gradlew.bat
55 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | android:
3 | components:
4 | - platform-tools
5 | - android-22
6 | - build-tools-22.0.1
7 |
8 | script:
9 | - gradle clean build
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Microsoft Open Technologies, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## MSA Auth for Android
2 |
3 | [  ](https://bintray.com/msopentech/Maven/msa-auth-for-android/_latestVersion)
4 | [](https://travis-ci.org/MSOpenTech/msa-auth-for-android)
5 |
6 | The MSA Auth for Android library is intended to help developers to easily integrate OneDrive and OneNote into their Android apps.
7 |
8 | ## Contributing
9 | You will need to sign a [Contributor License Agreement](https://cla.msopentech.com/) before submitting your pull request. To complete the Contributor License Agreement (CLA), you will need to submit a request via the form and then electronically sign the Contributor License Agreement when you receive the email containing the link to the document. This needs to only be done once for any Microsoft Open Technologies OSS project.
10 |
11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
12 |
13 | ## License
14 | Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Licensed under the MIT License, Version 2.0.
15 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'maven'
3 |
4 | buildscript {
5 | repositories {
6 | jcenter()
7 | }
8 |
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:1.2.3'
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | jcenter()
17 | maven {
18 | url project.nightliesUrl
19 | }
20 | }
21 | }
22 |
23 | if (JavaVersion.current().isJava8Compatible()) {
24 | allprojects {
25 | tasks.withType(Javadoc) {
26 | options.addStringOption('Xdoclint:none', '-quiet')
27 | }
28 | }
29 | }
30 |
31 | android {
32 | compileSdkVersion 22
33 | buildToolsVersion "22.0.1"
34 |
35 | // squelching errors from msa
36 | android {
37 | lintOptions {
38 | abortOnError false
39 | }
40 | }
41 |
42 | defaultConfig {
43 | minSdkVersion 14
44 | targetSdkVersion 22
45 | versionCode 1
46 | versionName "1.0"
47 | }
48 | buildTypes {
49 | release {
50 | minifyEnabled false
51 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
52 | }
53 | }
54 | }
55 |
56 | dependencies {
57 | compile fileTree(dir: 'libs', include: ['*.jar'])
58 | }
59 |
60 |
61 | uploadArchives {
62 | configuration = configurations.archives
63 | repositories.mavenDeployer {
64 | pom {
65 | setGroupId project.mavenGroupId
66 | setArtifactId project.mavenArtifactId
67 | setVersion project.mavenVersion
68 | }
69 | repository (url: project.mavenRepoUrl) {
70 | authentication(
71 | // put these values in local file ~/.gradle/gradle.properties
72 | userName: project.hasProperty("bintrayUsername") ? project.bintrayUsername : "",
73 | password: project.hasProperty("bintrayApikey") ? project.bintrayApikey : ""
74 | )
75 | }
76 | }
77 | }
78 |
79 | task javadoc(type: Javadoc) {
80 | source = android.sourceSets.main.java.srcDirs
81 | classpath += project.files(android.getBootClasspath())
82 | classpath += configurations.compile
83 | }
84 |
85 | task javadocJar(type: Jar, dependsOn: javadoc) {
86 | classifier = 'javadoc'
87 | from javadoc.destinationDir
88 | }
89 |
90 | task sourcesJar(type: Jar) {
91 | classifier = 'sources'
92 | from android.sourceSets.main.java.srcDirs
93 | }
94 |
95 | artifacts {
96 | archives javadocJar
97 | archives sourcesJar
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # These properties are referenced for Maven repo upload
2 |
3 | mavenRepoUrl = https://api.bintray.com/maven/msopentech/Maven/msa-auth-for-android
4 | mavenGroupId = com.microsoft.services.msa
5 | mavenArtifactId = msa-auth
6 | mavenVersion = 0.8.6
7 | nightliesUrl = http://dl.bintray.com/msopentech/Maven
8 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/AccessTokenRequest.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import java.util.List;
26 | import java.util.Locale;
27 |
28 | import org.apache.http.NameValuePair;
29 | import org.apache.http.client.HttpClient;
30 | import org.apache.http.message.BasicNameValuePair;
31 |
32 | import android.text.TextUtils;
33 |
34 | /**
35 | * AccessTokenRequest represents a request for an Access Token.
36 | * It subclasses the abstract class TokenRequest, which does most of the work.
37 | * This class adds the proper parameters for the access token request via the
38 | * constructBody() hook.
39 | */
40 | class AccessTokenRequest extends TokenRequest {
41 |
42 | /**
43 | * REQUIRED. The authorization code received from the
44 | * authorization server.
45 | */
46 | private final String code;
47 |
48 | /** REQUIRED. Value MUST be set to "authorization_code". */
49 | private final OAuth.GrantType grantType;
50 |
51 | /**
52 | * Constructs a new AccessTokenRequest, and initializes its member variables
53 | *
54 | * @param client the HttpClient to make HTTP requests on
55 | * @param clientId the client_id of the calling application
56 | * @param code the authorization code received from the AuthorizationRequest
57 | */
58 | public AccessTokenRequest(final HttpClient client,
59 | final String clientId,
60 | final String code,
61 | final OAuthConfig oAuthConfig) {
62 | super(client, clientId, oAuthConfig);
63 |
64 | if (TextUtils.isEmpty(code))
65 | throw new AssertionError();
66 |
67 | this.code = code;
68 | this.grantType = OAuth.GrantType.AUTHORIZATION_CODE;
69 | }
70 |
71 | /**
72 | * Adds the "code", "redirect_uri", and "grant_type" parameters to the body.
73 | *
74 | * @param body the list of NameValuePairs to be placed in the body of the HTTP request
75 | */
76 | @Override
77 | protected void constructBody(List body) {
78 | body.add(new BasicNameValuePair(OAuth.CODE, this.code));
79 | body.add(new BasicNameValuePair(OAuth.REDIRECT_URI, mOAuthConfig.getDesktopUri().toString()));
80 | body.add(new BasicNameValuePair(OAuth.GRANT_TYPE,
81 | this.grantType.toString().toLowerCase(Locale.US)));
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/AuthorizationRequest.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import android.annotation.SuppressLint;
26 | import android.app.Activity;
27 | import android.app.Dialog;
28 | import android.content.Context;
29 | import android.content.DialogInterface;
30 | import android.content.DialogInterface.OnCancelListener;
31 | import android.content.SharedPreferences;
32 | import android.content.SharedPreferences.Editor;
33 | import android.net.Uri;
34 | import android.os.AsyncTask;
35 | import android.os.Bundle;
36 | import android.text.TextUtils;
37 | import android.view.View;
38 | import android.view.ViewGroup.LayoutParams;
39 | import android.webkit.CookieManager;
40 | import android.webkit.CookieSyncManager;
41 | import android.webkit.WebSettings;
42 | import android.webkit.WebView;
43 | import android.webkit.WebViewClient;
44 | import android.widget.LinearLayout;
45 |
46 | import org.apache.http.client.HttpClient;
47 |
48 | import java.util.Arrays;
49 | import java.util.Comparator;
50 | import java.util.HashMap;
51 | import java.util.HashSet;
52 | import java.util.Locale;
53 | import java.util.Map;
54 | import java.util.Set;
55 |
56 |
57 | /**
58 | * AuthorizationRequest performs an Authorization Request by launching a WebView Dialog that
59 | * displays the login and consent page and then, on a successful login and consent, performs an
60 | * async AccessToken request.
61 | */
62 | class AuthorizationRequest implements ObservableOAuthRequest, OAuthRequestObserver {
63 |
64 | /**
65 | * OAuthDialog is a Dialog that contains a WebView. The WebView loads the passed in Uri, and
66 | * loads the passed in WebViewClient that allows the WebView to be observed (i.e., when a page
67 | * loads the WebViewClient will be notified).
68 | */
69 | private class OAuthDialog extends Dialog implements OnCancelListener {
70 |
71 | /**
72 | * AuthorizationWebViewClient is a static (i.e., does not have access to the instance that
73 | * created it) class that checks for when the end_uri is loaded in to the WebView and calls
74 | * the AuthorizationRequest's onEndUri method.
75 | */
76 | private class AuthorizationWebViewClient extends WebViewClient {
77 |
78 | private final CookieManager cookieManager;
79 | private final Set cookieKeys;
80 |
81 | public AuthorizationWebViewClient() {
82 | // I believe I need to create a syncManager before I can use a cookie manager.
83 | CookieSyncManager.createInstance(getContext());
84 | this.cookieManager = CookieManager.getInstance();
85 | this.cookieKeys = new HashSet();
86 | }
87 |
88 | /**
89 | * Call back used when a page is being started.
90 | *
91 | * This will check to see if the given URL is one of the end_uris/redirect_uris and
92 | * based on the query parameters the method will either return an error, or proceed with
93 | * an AccessTokenRequest.
94 | *
95 | * @param view {@link WebView} that this is attached to.
96 | * @param url of the page being started
97 | */
98 | @Override
99 | public void onPageFinished(WebView view, String url) {
100 | Uri uri = Uri.parse(url);
101 |
102 | // only clear cookies that are on the logout domain.
103 | if (mOAuthConfig.getLogoutUri().getHost().equals(uri.getHost())) {
104 | this.saveCookiesInMemory(this.cookieManager.getCookie(url));
105 | }
106 |
107 | Uri endUri = mOAuthConfig.getDesktopUri();
108 | boolean isEndUri = UriComparator.INSTANCE.compare(uri, endUri) == 0;
109 | if (!isEndUri) {
110 | return;
111 | }
112 |
113 | this.saveCookiesToPreferences();
114 |
115 | AuthorizationRequest.this.onEndUri(uri);
116 | dismissDialog();
117 | }
118 |
119 | private void dismissDialog() {
120 | if (isShowing() && activity != null && !activity.isFinishing()) {
121 | OAuthDialog.this.dismiss();
122 | }
123 | }
124 |
125 | /**
126 | * Callback when the WebView received an Error.
127 | *
128 | * This method will notify the listener about the error and dismiss the WebView dialog.
129 | *
130 | * @param view the WebView that received the error
131 | * @param errorCode the error code corresponding to a WebViewClient.ERROR_* value
132 | * @param description the String containing the description of the error
133 | * @param failingUrl the url that encountered an error
134 | */
135 | @Override
136 | public void onReceivedError(WebView view,
137 | int errorCode,
138 | String description,
139 | String failingUrl) {
140 | if (errorCode == WebViewClient.ERROR_UNSUPPORTED_SCHEME) {
141 | return;
142 | }
143 |
144 | AuthorizationRequest.this.onError("", description, failingUrl);
145 | dismissDialog();
146 | }
147 |
148 | private void saveCookiesInMemory(String cookie) {
149 | // Not all URLs will have cookies
150 | if (TextUtils.isEmpty(cookie)) {
151 | return;
152 | }
153 |
154 | String[] pairs = TextUtils.split(cookie, "; ");
155 | for (String pair : pairs) {
156 | int index = pair.indexOf(EQUALS);
157 | String key = pair.substring(0, index);
158 | this.cookieKeys.add(key);
159 | }
160 | }
161 |
162 | private void saveCookiesToPreferences() {
163 | SharedPreferences preferences =
164 | getContext().getSharedPreferences(PreferencesConstants.FILE_NAME,
165 | Context.MODE_PRIVATE);
166 |
167 | // If the application tries to login twice, before calling logout, there could
168 | // be a cookie that was sent on the first login, that was not sent in the second
169 | // login. So, read the cookies in that was saved before, and perform a union
170 | // with the new cookies.
171 | String value = preferences.getString(PreferencesConstants.COOKIES_KEY, "");
172 | String[] valueSplit = TextUtils.split(value, PreferencesConstants.COOKIE_DELIMITER);
173 |
174 | this.cookieKeys.addAll(Arrays.asList(valueSplit));
175 |
176 | Editor editor = preferences.edit();
177 | value = TextUtils.join(PreferencesConstants.COOKIE_DELIMITER, this.cookieKeys);
178 | editor.putString(PreferencesConstants.COOKIES_KEY, value);
179 | editor.commit();
180 |
181 | // we do not need to hold on to the cookieKeys in memory anymore.
182 | // It could be garbage collected when this object does, but let's clear it now,
183 | // since it will not be used again in the future.
184 | this.cookieKeys.clear();
185 | }
186 | }
187 |
188 | private WebView webView;
189 | /** Uri to load */
190 | private final Uri requestUri;
191 |
192 | /**
193 | * Constructs a new OAuthDialog.
194 | *
195 | * @param requestUri to load in the WebView
196 | */
197 | public OAuthDialog(Uri requestUri) {
198 | super(AuthorizationRequest.this.activity, android.R.style.Theme_Translucent_NoTitleBar);
199 | this.setOwnerActivity(AuthorizationRequest.this.activity);
200 |
201 | if (requestUri == null) throw new AssertionError();
202 | this.requestUri = requestUri;
203 | }
204 |
205 | /** Called when the user hits the back button on the dialog. */
206 | @Override
207 | public void onCancel(DialogInterface dialog) {
208 | LiveAuthException exception = new LiveAuthException(ErrorMessages.SIGNIN_CANCEL);
209 | AuthorizationRequest.this.onException(exception);
210 | }
211 |
212 | @SuppressLint("SetJavaScriptEnabled")
213 | @Override
214 | protected void onCreate(Bundle savedInstanceState) {
215 | super.onCreate(savedInstanceState);
216 |
217 | this.setOnCancelListener(this);
218 |
219 | final LinearLayout webViewContainer = new LinearLayout(this.getContext());
220 |
221 | if (webView == null) {
222 | webView = new WebView(this.getContext());
223 | webView.setWebViewClient(new AuthorizationWebViewClient());
224 | final WebSettings webSettings = webView.getSettings();
225 | webSettings.setJavaScriptEnabled(true);
226 | webView.loadUrl(this.requestUri.toString());
227 | webView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
228 | LayoutParams.MATCH_PARENT));
229 | webView.setVisibility(View.VISIBLE);
230 | }
231 | webViewContainer.addView(webView);
232 | webViewContainer.setVisibility(View.VISIBLE);
233 | webViewContainer.forceLayout();
234 |
235 | this.addContentView(webViewContainer, new LayoutParams(LayoutParams.MATCH_PARENT,
236 | LayoutParams.MATCH_PARENT));
237 | }
238 | }
239 |
240 | /**
241 | * Compares just the scheme, authority, and path. It does not compare the query parameters or
242 | * the fragment.
243 | */
244 | private enum UriComparator implements Comparator {
245 | INSTANCE;
246 |
247 | @Override
248 | public int compare(Uri lhs, Uri rhs) {
249 | String[] lhsParts = { lhs.getScheme(), lhs.getAuthority(), lhs.getPath() };
250 | String[] rhsParts = { rhs.getScheme(), rhs.getAuthority(), rhs.getPath() };
251 |
252 | if (lhsParts.length != rhsParts.length) throw new AssertionError();
253 | for (int i = 0; i < lhsParts.length; i++) {
254 | if (lhsParts[i] == null && rhsParts[i] == null) {
255 | return 0;
256 | }
257 |
258 | int compare = lhsParts[i].compareTo(rhsParts[i]);
259 | if (compare != 0) {
260 | return compare;
261 | }
262 | }
263 |
264 | return 0;
265 | }
266 | }
267 |
268 | private static final String AMPERSAND = "&";
269 | private static final String EQUALS = "=";
270 |
271 | /**
272 | * Turns the fragment parameters of the uri into a map.
273 | *
274 | * @param uri to get fragment parameters from
275 | * @return a map containing the fragment parameters
276 | */
277 | private static Map getFragmentParametersMap(Uri uri) {
278 | String fragment = uri.getFragment();
279 | String[] keyValuePairs = TextUtils.split(fragment, AMPERSAND);
280 | Map fragementParameters = new HashMap();
281 |
282 | for (String keyValuePair : keyValuePairs) {
283 | int index = keyValuePair.indexOf(EQUALS);
284 | String key = keyValuePair.substring(0, index);
285 | String value = keyValuePair.substring(index + 1);
286 | fragementParameters.put(key, value);
287 | }
288 |
289 | return fragementParameters;
290 | }
291 |
292 | private final Activity activity;
293 | private final HttpClient client;
294 | private final String clientId;
295 | private final DefaultObservableOAuthRequest observable;
296 | private final String scope;
297 | private final String loginHint;
298 | private final OAuthConfig mOAuthConfig;
299 |
300 | public AuthorizationRequest(Activity activity,
301 | HttpClient client,
302 | String clientId,
303 | String scope,
304 | String loginHint,
305 | final OAuthConfig oAuthConfig) {
306 | if (activity == null) throw new AssertionError();
307 | if (client == null) throw new AssertionError();
308 | if (TextUtils.isEmpty(clientId)) throw new AssertionError();
309 | if (TextUtils.isEmpty(scope)) throw new AssertionError();
310 |
311 | this.activity = activity;
312 | this.client = client;
313 | this.clientId = clientId;
314 | this.mOAuthConfig = oAuthConfig;
315 | this.observable = new DefaultObservableOAuthRequest();
316 | this.scope = scope;
317 | this.loginHint = loginHint;
318 | }
319 |
320 | @Override
321 | public void addObserver(OAuthRequestObserver observer) {
322 | this.observable.addObserver(observer);
323 | }
324 |
325 | /**
326 | * Launches the login/consent page inside of a Dialog that contains a WebView and then performs
327 | * a AccessTokenRequest on successful login and consent. This method is async and will call the
328 | * passed in listener when it is completed.
329 | */
330 | public void execute() {
331 | String displayType = this.getDisplayParameter();
332 | String responseType = OAuth.ResponseType.CODE.toString().toLowerCase(Locale.US);
333 | String locale = Locale.getDefault().toString();
334 | final Uri.Builder requestUriBuilder = mOAuthConfig.getAuthorizeUri()
335 | .buildUpon()
336 | .appendQueryParameter(OAuth.CLIENT_ID, this.clientId)
337 | .appendQueryParameter(OAuth.SCOPE, this.scope)
338 | .appendQueryParameter(OAuth.DISPLAY, displayType)
339 | .appendQueryParameter(OAuth.RESPONSE_TYPE, responseType)
340 | .appendQueryParameter(OAuth.REDIRECT_URI, mOAuthConfig.getDesktopUri().toString());
341 |
342 | if (this.loginHint != null) {
343 | requestUriBuilder.appendQueryParameter(OAuth.LOGIN_HINT, this.loginHint);
344 | requestUriBuilder.appendQueryParameter(OAuth.USER_NAME, this.loginHint);
345 | }
346 |
347 | Uri requestUri = requestUriBuilder.build();
348 |
349 | OAuthDialog oAuthDialog = new OAuthDialog(requestUri);
350 | oAuthDialog.show();
351 | }
352 |
353 | @Override
354 | public void onException(LiveAuthException exception) {
355 | this.observable.notifyObservers(exception);
356 | }
357 |
358 | @Override
359 | public void onResponse(OAuthResponse response) {
360 | this.observable.notifyObservers(response);
361 | }
362 |
363 | @Override
364 | public boolean removeObserver(OAuthRequestObserver observer) {
365 | return this.observable.removeObserver(observer);
366 | }
367 |
368 | /**
369 | * Gets the display parameter by looking at the screen size of the activity.
370 | * @return "android_phone" for phones and "android_tablet" for tablets.
371 | */
372 | private String getDisplayParameter() {
373 | ScreenSize screenSize = ScreenSize.determineScreenSize(this.activity);
374 | DeviceType deviceType = screenSize.getDeviceType();
375 |
376 | return deviceType.getDisplayParameter().toString().toLowerCase(Locale.US);
377 | }
378 |
379 | /**
380 | * Called when the response uri contains an access_token in the fragment.
381 | *
382 | * This method reads the response and calls back the LiveOAuthListener on the UI/main thread,
383 | * and then dismisses the dialog window.
384 | *
385 | * See Section
386 | * 1.3.1 of the OAuth 2.0 spec.
387 | *
388 | * @param fragmentParameters in the uri
389 | */
390 | private void onAccessTokenResponse(Map fragmentParameters) {
391 | if (fragmentParameters == null) throw new AssertionError();
392 |
393 | OAuthSuccessfulResponse response;
394 | try {
395 | response = OAuthSuccessfulResponse.createFromFragment(fragmentParameters);
396 | } catch (LiveAuthException e) {
397 | this.onException(e);
398 | return;
399 | }
400 |
401 | this.onResponse(response);
402 | }
403 |
404 | /**
405 | * Called when the response uri contains an authorization code.
406 | *
407 | * This method launches an async AccessTokenRequest and dismisses the dialog window.
408 | *
409 | * See Section
410 | * 4.1.2 of the OAuth 2.0 spec for more information.
411 | *
412 | * @param code is the authorization code from the uri
413 | */
414 | private void onAuthorizationResponse(String code) {
415 | if (TextUtils.isEmpty(code)) throw new AssertionError();
416 |
417 | // Since we DO have an authorization code, launch an AccessTokenRequest.
418 | // We do this asynchronously to prevent the HTTP IO from occupying the
419 | // UI/main thread (which we are on right now).
420 | AccessTokenRequest request = new AccessTokenRequest(this.client,
421 | this.clientId,
422 | code,
423 | mOAuthConfig);
424 |
425 | TokenRequestAsync requestAsync = new TokenRequestAsync(request);
426 | // We want to know when this request finishes, because we need to notify our
427 | // observers.
428 | requestAsync.addObserver(this);
429 | requestAsync.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
430 | }
431 |
432 | /**
433 | * Called when the end uri is loaded.
434 | *
435 | * This method will read the uri's query parameters and fragment, and respond with the
436 | * appropriate action.
437 | *
438 | * @param endUri that was loaded
439 | */
440 | private void onEndUri(Uri endUri) {
441 | // If we are on an end uri, the response could either be in
442 | // the fragment or the query parameters. The response could
443 | // either be successful or it could contain an error.
444 | // Check all situations and call the listener's appropriate callback.
445 | // Callback the listener on the UI/main thread. We could call it right away since
446 | // we are on the UI/main thread, but it is probably better that we finish up with
447 | // the WebView code before we callback on the listener.
448 | boolean hasFragment = endUri.getFragment() != null;
449 | boolean hasQueryParameters = endUri.getQuery() != null;
450 | boolean invalidUri = !hasFragment && !hasQueryParameters;
451 | boolean isHierarchical = endUri.isHierarchical();
452 |
453 | // check for an invalid uri, and leave early
454 | if (invalidUri) {
455 | this.onInvalidUri();
456 | return;
457 | }
458 |
459 | if (hasFragment) {
460 | Map fragmentParameters =
461 | AuthorizationRequest.getFragmentParametersMap(endUri);
462 |
463 | boolean isSuccessfulResponse =
464 | fragmentParameters.containsKey(OAuth.ACCESS_TOKEN) &&
465 | fragmentParameters.containsKey(OAuth.TOKEN_TYPE);
466 | if (isSuccessfulResponse) {
467 | this.onAccessTokenResponse(fragmentParameters);
468 | return;
469 | }
470 |
471 | String error = fragmentParameters.get(OAuth.ERROR);
472 | if (error != null) {
473 | String errorDescription = fragmentParameters.get(OAuth.ERROR_DESCRIPTION);
474 | String errorUri = fragmentParameters.get(OAuth.ERROR_URI);
475 | this.onError(error, errorDescription, errorUri);
476 | return;
477 | }
478 | }
479 |
480 | if (hasQueryParameters && isHierarchical) {
481 | String code = endUri.getQueryParameter(OAuth.CODE);
482 | if (code != null) {
483 | this.onAuthorizationResponse(code);
484 | return;
485 | }
486 |
487 | String error = endUri.getQueryParameter(OAuth.ERROR);
488 | if (error != null) {
489 | String errorDescription = endUri.getQueryParameter(OAuth.ERROR_DESCRIPTION);
490 | String errorUri = endUri.getQueryParameter(OAuth.ERROR_URI);
491 | this.onError(error, errorDescription, errorUri);
492 | return;
493 | }
494 | }
495 |
496 | if (hasQueryParameters && !isHierarchical) {
497 | String[] pairs = endUri.getQuery().split("&|=");
498 | for (int i = 0; i < pairs.length; i =+ 2) {
499 | if (pairs[i].equals(OAuth.CODE)) {
500 | this.onAuthorizationResponse(pairs[i + 1]);
501 | return;
502 | }
503 | }
504 | }
505 |
506 | // if the code reaches this point, the uri was invalid
507 | // because it did not contain either a successful response
508 | // or an error in either the queryParameter or the fragment
509 | this.onInvalidUri();
510 | }
511 |
512 | /**
513 | * Called when end uri had an error in either the fragment or the query parameter.
514 | *
515 | * This method constructs the proper exception, calls the listener's appropriate callback method
516 | * on the main/UI thread, and then dismisses the dialog window.
517 | *
518 | * @param error containing an error code
519 | * @param errorDescription optional text with additional information
520 | * @param errorUri optional uri that is associated with the error.
521 | */
522 | private void onError(String error, String errorDescription, String errorUri) {
523 | LiveAuthException exception = new LiveAuthException(error,
524 | errorDescription,
525 | errorUri);
526 | this.onException(exception);
527 | }
528 |
529 | /**
530 | * Called when an invalid uri (i.e., a uri that does not contain an error or a successful
531 | * response).
532 | *
533 | * This method constructs an exception, calls the listener's appropriate callback on the main/UI
534 | * thread, and then dismisses the dialog window.
535 | */
536 | private void onInvalidUri() {
537 | LiveAuthException exception = new LiveAuthException(ErrorMessages.SERVER_ERROR);
538 | this.onException(exception);
539 | }
540 | }
541 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/Config.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import android.net.Uri;
26 | import android.text.TextUtils;
27 |
28 | /**
29 | * Config is a singleton class that contains the values used throughout the SDK.
30 | */
31 | enum Config {
32 | INSTANCE;
33 |
34 | private Uri apiUri;
35 | private String apiVersion;
36 |
37 | Config() {
38 | // initialize default values for constants
39 | apiUri = Uri.parse("https://apis.live.net/v5.0");
40 | apiVersion = "5.0";
41 | }
42 |
43 | public Uri getApiUri() {
44 | return apiUri;
45 | }
46 |
47 | public String getApiVersion() {
48 | return apiVersion;
49 | }
50 |
51 | public void setApiUri(Uri apiUri) {
52 | assert apiUri != null;
53 | this.apiUri = apiUri;
54 | }
55 |
56 | public void setApiVersion(String apiVersion) {
57 | if (TextUtils.isEmpty(apiVersion)) throw new AssertionError();
58 | this.apiVersion = apiVersion;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/DefaultObservableOAuthRequest.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import java.util.ArrayList;
26 | import java.util.List;
27 |
28 | /**
29 | * Default implementation of an ObserverableOAuthRequest.
30 | * Other classes that need to be observed can compose themselves out of this class.
31 | */
32 | class DefaultObservableOAuthRequest implements ObservableOAuthRequest {
33 |
34 | private final List observers;
35 |
36 | public DefaultObservableOAuthRequest() {
37 | this.observers = new ArrayList();
38 | }
39 |
40 | @Override
41 | public void addObserver(OAuthRequestObserver observer) {
42 | this.observers.add(observer);
43 | }
44 |
45 | /**
46 | * Calls all the Observerable's observer's onException.
47 | *
48 | * @param exception to give to the observers
49 | */
50 | public void notifyObservers(LiveAuthException exception) {
51 | for (final OAuthRequestObserver observer : this.observers) {
52 | observer.onException(exception);
53 | }
54 | }
55 |
56 | /**
57 | * Calls all this Observable's observer's onResponse.
58 | *
59 | * @param response to give to the observers
60 | */
61 | public void notifyObservers(OAuthResponse response) {
62 | for (final OAuthRequestObserver observer : this.observers) {
63 | observer.onResponse(response);
64 | }
65 | }
66 |
67 | @Override
68 | public boolean removeObserver(OAuthRequestObserver observer) {
69 | return this.observers.remove(observer);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/DeviceType.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * The type of the device is used to determine the display query parameter for login.live.com.
27 | * Phones have a display parameter of android_phone.
28 | * Tablets have a display parameter of android_tablet.
29 | */
30 | enum DeviceType {
31 | PHONE {
32 | @Override
33 | public OAuth.DisplayType getDisplayParameter() {
34 | return OAuth.DisplayType.ANDROID_PHONE;
35 | }
36 | },
37 | TABLET {
38 | @Override
39 | public OAuth.DisplayType getDisplayParameter() {
40 | return OAuth.DisplayType.ANDROID_TABLET;
41 | }
42 | };
43 |
44 | abstract public OAuth.DisplayType getDisplayParameter();
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/ErrorMessages.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * ErrorMessages is a non-instantiable class that contains all the String constants
27 | * used in for errors and exceptions.
28 | */
29 | final class ErrorMessages {
30 | public static final String ABSOLUTE_PARAMETER =
31 | "Input parameter '%1$s' is invalid. '%1$s' cannot be absolute.";
32 | public static final String CLIENT_ERROR =
33 | "An error occured on the client during the operation.";
34 | public static final String EMPTY_PARAMETER =
35 | "Input parameter '%1$s' is invalid. '%1$s' cannot be empty.";
36 | public static final String INVALID_URI =
37 | "Input parameter '%1$s' is invalid. '%1$s' must be a valid URI.";
38 | public static final String LOGGED_OUT = "The user has is logged out.";
39 | public static final String LOGIN_IN_PROGRESS =
40 | "Another login operation is already in progress.";
41 | public static final String MISSING_UPLOAD_LOCATION =
42 | "The provided path does not contain an upload_location.";
43 | public static final String NON_INSTANTIABLE_CLASS = "Non-instantiable class";
44 | public static final String NULL_PARAMETER =
45 | "Input parameter '%1$s' is invalid. '%1$s' cannot be null.";
46 | public static final String SERVER_ERROR =
47 | "An error occured while communicating with the server during the operation. " +
48 | "Please try again later.";
49 | public static final String SIGNIN_CANCEL = "The user cancelled the login operation.";
50 |
51 | private ErrorMessages() { throw new AssertionError(NON_INSTANTIABLE_CLASS); }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/LiveAuthClient.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import android.app.Activity;
26 | import android.app.Dialog;
27 | import android.content.Context;
28 | import android.content.SharedPreferences;
29 | import android.content.SharedPreferences.Editor;
30 | import android.net.Uri;
31 | import android.os.AsyncTask;
32 | import android.os.Build;
33 | import android.text.TextUtils;
34 | import android.util.Log;
35 | import android.webkit.CookieManager;
36 | import android.webkit.CookieSyncManager;
37 |
38 | import org.apache.http.client.HttpClient;
39 | import org.apache.http.impl.client.DefaultHttpClient;
40 |
41 | import java.util.Arrays;
42 | import java.util.Collections;
43 | import java.util.HashSet;
44 | import java.util.List;
45 | import java.util.Locale;
46 | import java.util.Set;
47 |
48 | /**
49 | * {@code LiveAuthClient} is a class responsible for retrieving a {@link LiveConnectSession}, which
50 | * provides authentication information for calls to Live APIs.
51 | */
52 | public class LiveAuthClient {
53 |
54 | private static final String TAG = "LiveAuthClient";
55 |
56 | private static class AuthCompleteRunnable extends AuthListenerCaller implements Runnable {
57 |
58 | private final LiveStatus status;
59 | private final LiveConnectSession session;
60 |
61 | public AuthCompleteRunnable(LiveAuthListener listener,
62 | Object userState,
63 | LiveStatus status,
64 | LiveConnectSession session) {
65 | super(listener, userState);
66 | this.status = status;
67 | this.session = session;
68 | }
69 |
70 | @Override
71 | public void run() {
72 | listener.onAuthComplete(status, session, userState);
73 | }
74 | }
75 |
76 | private static class AuthErrorRunnable extends AuthListenerCaller implements Runnable {
77 |
78 | private final LiveAuthException exception;
79 |
80 | public AuthErrorRunnable(LiveAuthListener listener,
81 | Object userState,
82 | LiveAuthException exception) {
83 | super(listener, userState);
84 | this.exception = exception;
85 | }
86 |
87 | @Override
88 | public void run() {
89 | listener.onAuthError(exception, userState);
90 | }
91 |
92 | }
93 |
94 | private static abstract class AuthListenerCaller {
95 | protected final LiveAuthListener listener;
96 | protected final Object userState;
97 |
98 | public AuthListenerCaller(LiveAuthListener listener, Object userState) {
99 | this.listener = listener;
100 | this.userState = userState;
101 | }
102 | }
103 |
104 | /**
105 | * This class observes an {@link AccessTokenRequest} and calls the appropriate Listener method.
106 | * On a successful response, it will call the
107 | * {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
108 | * On an exception or an unsuccessful response, it will call
109 | * {@link LiveAuthListener#onAuthError(LiveAuthException, Object)}.
110 | */
111 | private class ListenerCallerObserver extends AuthListenerCaller
112 | implements OAuthRequestObserver,
113 | OAuthResponseVisitor {
114 |
115 | public ListenerCallerObserver(LiveAuthListener listener, Object userState) {
116 | super(listener, userState);
117 | }
118 |
119 | @Override
120 | public void onException(LiveAuthException exception) {
121 | new AuthErrorRunnable(listener, userState, exception).run();
122 | }
123 |
124 | @Override
125 | public void onResponse(OAuthResponse response) {
126 | response.accept(this);
127 | }
128 |
129 | @Override
130 | public void visit(OAuthErrorResponse response) {
131 | String error = response.getError().toString().toLowerCase(Locale.US);
132 | String errorDescription = response.getErrorDescription();
133 | String errorUri = response.getErrorUri();
134 | LiveAuthException exception = new LiveAuthException(error,
135 | errorDescription,
136 | errorUri);
137 |
138 | new AuthErrorRunnable(listener, userState, exception).run();
139 | }
140 |
141 | @Override
142 | public void visit(OAuthSuccessfulResponse response) {
143 | session.loadFromOAuthResponse(response);
144 |
145 | new AuthCompleteRunnable(listener, userState, LiveStatus.CONNECTED, session).run();
146 | }
147 | }
148 |
149 | /** Observer that will, depending on the response, save or clear the refresh token. */
150 | private class RefreshTokenWriter implements OAuthRequestObserver, OAuthResponseVisitor {
151 |
152 | @Override
153 | public void onException(LiveAuthException exception) { }
154 |
155 | @Override
156 | public void onResponse(OAuthResponse response) {
157 | response.accept(this);
158 | }
159 |
160 | @Override
161 | public void visit(OAuthErrorResponse response) {
162 | if (response.getError() == OAuth.ErrorType.INVALID_GRANT) {
163 | LiveAuthClient.this.clearRefreshTokenFromPreferences();
164 | }
165 | }
166 |
167 | @Override
168 | public void visit(OAuthSuccessfulResponse response) {
169 | String refreshToken = response.getRefreshToken();
170 | if (!TextUtils.isEmpty(refreshToken)) {
171 | this.saveRefreshTokenToPreferences(refreshToken);
172 | }
173 | }
174 |
175 | private boolean saveRefreshTokenToPreferences(String refreshToken) {
176 | if (TextUtils.isEmpty(refreshToken)) throw new AssertionError();
177 |
178 | SharedPreferences settings =
179 | applicationContext.getSharedPreferences(PreferencesConstants.FILE_NAME,
180 | Context.MODE_PRIVATE);
181 | Editor editor = settings.edit();
182 | editor.putString(PreferencesConstants.REFRESH_TOKEN_KEY, refreshToken);
183 |
184 | return editor.commit();
185 | }
186 | }
187 |
188 | /**
189 | * An {@link OAuthResponseVisitor} that checks the {@link OAuthResponse} and if it is a
190 | * successful response, it loads the response into the given session.
191 | */
192 | private static class SessionRefresher implements OAuthResponseVisitor {
193 |
194 | private final LiveConnectSession session;
195 | private boolean visitedSuccessfulResponse;
196 |
197 | public SessionRefresher(LiveConnectSession session) {
198 | if (session == null) throw new AssertionError();
199 |
200 | this.session = session;
201 | this.visitedSuccessfulResponse = false;
202 | }
203 |
204 | @Override
205 | public void visit(OAuthErrorResponse response) {
206 | this.visitedSuccessfulResponse = false;
207 | }
208 |
209 | @Override
210 | public void visit(OAuthSuccessfulResponse response) {
211 | this.session.loadFromOAuthResponse(response);
212 | this.visitedSuccessfulResponse = true;
213 | }
214 |
215 | public boolean visitedSuccessfulResponse() {
216 | return this.visitedSuccessfulResponse;
217 | }
218 | }
219 |
220 | /**
221 | * A LiveAuthListener that does nothing on each of the call backs.
222 | * This is used so when a null listener is passed in, this can be used, instead of null,
223 | * to avoid if (listener == null) checks.
224 | */
225 | private static final LiveAuthListener NULL_LISTENER = new LiveAuthListener() {
226 | @Override
227 | public void onAuthComplete(LiveStatus status, LiveConnectSession session, Object sender) { }
228 | @Override
229 | public void onAuthError(LiveAuthException exception, Object sender) { }
230 | };
231 |
232 | private final Context applicationContext;
233 | private final String clientId;
234 | private boolean hasPendingLoginRequest;
235 |
236 | /**
237 | * Responsible for all network (i.e., HTTP) calls.
238 | * Tests will want to change this to mock the network and HTTP responses.
239 | * @see #setHttpClient(HttpClient)
240 | */
241 | private HttpClient httpClient;
242 |
243 | /** saved from initialize and used in the login call if login's scopes are null. */
244 | private Set baseScopes;
245 |
246 | /** The OAuth configuration */
247 | private final OAuthConfig mOAuthConfig;
248 |
249 | /** One-to-one relationship between LiveAuthClient and LiveConnectSession. */
250 | private final LiveConnectSession session;
251 |
252 | {
253 | this.httpClient = new DefaultHttpClient();
254 | this.hasPendingLoginRequest = false;
255 | this.session = new LiveConnectSession(this);
256 | }
257 |
258 | /**
259 | * Constructs a new {@code LiveAuthClient} instance and initializes its member variables.
260 | *
261 | * @param context Context of the Application used to save any refresh_token.
262 | * @param clientId The client_id of the Live Connect Application to login to.
263 | * @param scopes to initialize the {@link LiveConnectSession} with.
264 | * See MSDN Live Connect
265 | * Reference's Scopes and permissions for a list of scopes and explanations.
266 | */
267 | public LiveAuthClient(final Context context,
268 | final String clientId,
269 | final Iterable scopes,
270 | final OAuthConfig oAuthConfig) {
271 | LiveConnectUtils.assertNotNull(context, "context");
272 | LiveConnectUtils.assertNotNullOrEmpty(clientId, "clientId");
273 |
274 | this.applicationContext = context.getApplicationContext();
275 | this.clientId = clientId;
276 |
277 |
278 | if (oAuthConfig == null) {
279 | this.mOAuthConfig = MicrosoftOAuthConfig.getInstance();
280 | } else {
281 | this.mOAuthConfig = oAuthConfig;
282 | }
283 |
284 | // copy scopes for login
285 | Iterable tempScopes = scopes;
286 | if (tempScopes == null) {
287 | tempScopes = Arrays.asList(new String[0]);
288 | }
289 |
290 | this.baseScopes = new HashSet<>();
291 | for (final String scope : tempScopes) {
292 | this.baseScopes.add(scope);
293 | }
294 |
295 | this.baseScopes = Collections.unmodifiableSet(this.baseScopes);
296 |
297 | final String refreshToken = this.getRefreshTokenFromPreferences();
298 | if (!TextUtils.isEmpty(refreshToken)) {
299 | final String scopeAsString = TextUtils.join(OAuth.SCOPE_DELIMITER, this.baseScopes);
300 | RefreshAccessTokenRequest request = new RefreshAccessTokenRequest(this.httpClient,
301 | this.clientId,
302 | refreshToken,
303 | scopeAsString,
304 | this.mOAuthConfig);
305 | TokenRequestAsync requestAsync = new TokenRequestAsync(request);
306 | requestAsync.addObserver(new RefreshTokenWriter());
307 | requestAsync.execute();
308 | }
309 | }
310 |
311 | public LiveAuthClient(final Context context, final String clientId) {
312 | this(context, clientId, null);
313 | }
314 |
315 | public LiveAuthClient(final Context context,
316 | final String clientId,
317 | final Iterable scopes) {
318 | this(context,clientId, scopes, null);
319 | }
320 |
321 | /** @return the client_id of the Live Connect application. */
322 | public String getClientId() {
323 | return this.clientId;
324 | }
325 |
326 | public void login(Activity activity, LiveAuthListener listener) {
327 | this.login(activity, null, null, listener);
328 | }
329 |
330 | public void login(Activity activity, Iterable scopes, LiveAuthListener listener) {
331 | this.login(activity, scopes, null, null, listener);
332 | }
333 |
334 | public void login(Activity activity, Iterable scopes, Object userState, LiveAuthListener listener) {
335 | this.login(activity, scopes, userState, null, listener);
336 | }
337 |
338 | /**
339 | * Logs in an user with the given scopes and additional saved state.
340 | *
341 | * login displays a {@link Dialog} that will prompt the
342 | * user for a username and password, and ask for consent to use the given scopes.
343 | * A {@link LiveConnectSession} will be returned by calling
344 | * {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
345 | * Otherwise, the {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be
346 | * called. These methods will be called on the main/UI thread.
347 | *
348 | * @param activity {@link Activity} instance to display the Login dialog on
349 | * @param scopes to initialize the {@link LiveConnectSession} with.
350 | * See MSDN Live Connect
351 | * Reference's Scopes and permissions for a list of scopes and explanations.
352 | * Scopes specified here override scopes specified in the constructor.
353 | * @param userState arbitrary object that is used to determine the caller of the method.
354 | * @param loginHint the hint for the sign in experience to show the username pre-filled out
355 | * @param listener called on either completion or error during the login process.
356 | * @throws IllegalStateException if there is a pending login request.
357 | */
358 | public void login(Activity activity,
359 | Iterable scopes,
360 | Object userState,
361 | String loginHint,
362 | LiveAuthListener listener
363 | ) {
364 | LiveConnectUtils.assertNotNull(activity, "activity");
365 |
366 | if (listener == null) {
367 | listener = NULL_LISTENER;
368 | }
369 |
370 | if (this.hasPendingLoginRequest) {
371 | throw new IllegalStateException(ErrorMessages.LOGIN_IN_PROGRESS);
372 | }
373 |
374 | // if no scopes were passed in, use the scopes from initialize or if those are empty,
375 | // create an empty list
376 | if (scopes == null) {
377 | if (this.baseScopes == null) {
378 | scopes = Arrays.asList(new String[0]);
379 | } else {
380 | scopes = this.baseScopes;
381 | }
382 | }
383 |
384 | // if the session is valid and contains all the scopes, do not display the login ui.
385 | if (loginSilent(scopes, userState, listener)) {
386 | Log.i(TAG, "Interactive login not required.");
387 | return;
388 | }
389 |
390 | // silent login failed, initiating interactive login
391 | String scope = TextUtils.join(OAuth.SCOPE_DELIMITER, scopes);
392 |
393 | AuthorizationRequest request = new AuthorizationRequest(activity,
394 | this.httpClient,
395 | this.clientId,
396 | scope,
397 | loginHint,
398 | mOAuthConfig);
399 |
400 | request.addObserver(new ListenerCallerObserver(listener, userState));
401 | request.addObserver(new RefreshTokenWriter());
402 | request.addObserver(new OAuthRequestObserver() {
403 | @Override
404 | public void onException(LiveAuthException exception) {
405 | LiveAuthClient.this.hasPendingLoginRequest = false;
406 | }
407 |
408 | @Override
409 | public void onResponse(OAuthResponse response) {
410 | LiveAuthClient.this.hasPendingLoginRequest = false;
411 | }
412 | });
413 |
414 | this.hasPendingLoginRequest = true;
415 |
416 | request.execute();
417 | }
418 |
419 | public Boolean loginSilent(LiveAuthListener listener) {
420 | return this.loginSilent(null, null, listener);
421 | }
422 |
423 | public Boolean loginSilent(Iterable scopes, LiveAuthListener listener) {
424 | return this.loginSilent(scopes, null, listener);
425 | }
426 |
427 | public Boolean loginSilent(Object userState, LiveAuthListener listener) {
428 | return this.loginSilent(null, userState, listener);
429 | }
430 |
431 | /**
432 | * Attempts to log in a user using multiple non-interactive approaches.
433 | *
434 | * A {@link LiveConnectSession} will be returned by calling
435 | * {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)}.
436 | * Otherwise, the {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be
437 | * called. These methods will be called on the main/UI thread.
438 | *
439 | * @param scopes list of scopes which will override scopes from constructor
440 | * @param userState state object that is pass to listener on completion.
441 | * @param listener called on either completion or error during the login process.
442 | * @return false == silent login failed, interactive login required.
443 | * true == silent login is continuing on the background thread the listener will be
444 | * called back when it has completed.
445 | */
446 | public Boolean loginSilent(final Iterable scopes,
447 | final Object userState,
448 | final LiveAuthListener listener) {
449 |
450 | if (this.hasPendingLoginRequest) {
451 | throw new IllegalStateException(ErrorMessages.LOGIN_IN_PROGRESS);
452 | }
453 |
454 | final Iterable activeScopes;
455 | if (scopes == null) {
456 | if (this.baseScopes == null) {
457 | activeScopes = Arrays.asList(new String[0]);
458 | } else {
459 | activeScopes = this.baseScopes;
460 | }
461 | } else {
462 | activeScopes = scopes;
463 | }
464 |
465 | if (TextUtils.isEmpty(this.session.getRefreshToken())) {
466 | this.session.setRefreshToken(getRefreshTokenFromPreferences());
467 | }
468 |
469 | // if the session is valid and contains all the scopes, do not display the login ui.
470 | final boolean needNewAccessToken = this.session.isExpired() || !this.session.contains(activeScopes);
471 | final boolean attemptingToLoginSilently = TextUtils.isEmpty(this.session.getRefreshToken());
472 |
473 | new AsyncTask() {
474 | @Override
475 | protected Void doInBackground(final Void... voids) {
476 | if (!needNewAccessToken) {
477 | Log.i(TAG, "Access token still valid, so using it.");
478 | listener.onAuthComplete(LiveStatus.CONNECTED, LiveAuthClient.this.session, userState);
479 | } else if (tryRefresh(activeScopes)) {
480 | Log.i(TAG, "Used refresh token to refresh access and refresh tokens.");
481 | listener.onAuthComplete(LiveStatus.CONNECTED, LiveAuthClient.this.session, userState);
482 | } else {
483 | Log.i(TAG, "All tokens expired, you need to call login() to initiate interactive logon");
484 | listener.onAuthComplete(LiveStatus.NOT_CONNECTED,
485 | LiveAuthClient.this.getSession(), userState);
486 | }
487 | return null;
488 | }
489 | }.execute();
490 |
491 | return !attemptingToLoginSilently;
492 | }
493 |
494 | /**
495 | * Logs out the given user.
496 | *
497 | * Also, this method clears the previously created {@link LiveConnectSession}.
498 | * {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)} will be
499 | * called on completion. Otherwise,
500 | * {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be called.
501 | *
502 | * @param listener called on either completion or error during the logout process.
503 | */
504 | public void logout(LiveAuthListener listener) {
505 | this.logout(null, listener);
506 | }
507 |
508 | /**
509 | * Logs out the given user.
510 | *
511 | * Also, this method clears the previously created {@link LiveConnectSession}.
512 | * {@link LiveAuthListener#onAuthComplete(LiveStatus, LiveConnectSession, Object)} will be
513 | * called on completion. Otherwise,
514 | * {@link LiveAuthListener#onAuthError(LiveAuthException, Object)} will be called.
515 | *
516 | * @param userState arbitrary object that is used to determine the caller of the method.
517 | * @param listener called on either completion or error during the logout process.
518 | */
519 | public void logout(Object userState, LiveAuthListener listener) {
520 | if (listener == null) {
521 | listener = NULL_LISTENER;
522 | }
523 |
524 | session.setAccessToken(null);
525 | session.setAuthenticationToken(null);
526 | session.setRefreshToken(null);
527 | session.setScopes(null);
528 | session.setTokenType(null);
529 |
530 | clearRefreshTokenFromPreferences();
531 |
532 | CookieSyncManager cookieSyncManager =
533 | CookieSyncManager.createInstance(this.applicationContext);
534 | CookieManager manager = CookieManager.getInstance();
535 |
536 | // clear cookies to force prompt on login
537 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
538 | manager.removeAllCookies(null);
539 | else
540 | manager.removeAllCookie();
541 |
542 | cookieSyncManager.sync();
543 | listener.onAuthComplete(LiveStatus.UNKNOWN, null, userState);
544 | }
545 |
546 | /** @return The {@link HttpClient} instance used by this {@code LiveAuthClient}. */
547 | HttpClient getHttpClient() {
548 | return this.httpClient;
549 | }
550 |
551 | /** @return The {@link LiveConnectSession} instance that this {@code LiveAuthClient} created. */
552 | public LiveConnectSession getSession() {
553 | return session;
554 | }
555 |
556 | /**
557 | * Refreshes the previously created session.
558 | *
559 | * @return true if the session was successfully refreshed.
560 | */
561 | Boolean tryRefresh(Iterable scopes) {
562 |
563 | String scope = TextUtils.join(OAuth.SCOPE_DELIMITER, scopes);
564 | String refreshToken = this.session.getRefreshToken();
565 |
566 | if (TextUtils.isEmpty(refreshToken)) {
567 | Log.i(TAG, "No refresh token available, sorry!");
568 | return false;
569 | }
570 |
571 | Log.i(TAG, "Refresh token found, attempting to refresh access and refresh tokens.");
572 | RefreshAccessTokenRequest request =
573 | new RefreshAccessTokenRequest(this.httpClient, this.clientId, refreshToken, scope, mOAuthConfig);
574 |
575 | OAuthResponse response;
576 | try {
577 | response = request.execute();
578 | } catch (LiveAuthException e) {
579 | return false;
580 | }
581 |
582 | SessionRefresher refresher = new SessionRefresher(this.session);
583 | response.accept(refresher);
584 | response.accept(new RefreshTokenWriter());
585 |
586 | return refresher.visitedSuccessfulResponse();
587 | }
588 |
589 | /**
590 | * Sets the {@link HttpClient} that is used for HTTP requests by this {@code LiveAuthClient}.
591 | * Tests will want to change this to mock the network/HTTP responses.
592 | * @param client The new HttpClient to be set.
593 | */
594 | void setHttpClient(HttpClient client) {
595 | if (client == null) throw new AssertionError();
596 | this.httpClient = client;
597 | }
598 |
599 | /**
600 | * Clears the refresh token from this {@code LiveAuthClient}'s
601 | * {@link Activity#getPreferences(int)}.
602 | *
603 | * @return true if the refresh token was successfully cleared.
604 | */
605 | private boolean clearRefreshTokenFromPreferences() {
606 | SharedPreferences settings = getSharedPreferences();
607 | Editor editor = settings.edit();
608 | editor.remove(PreferencesConstants.REFRESH_TOKEN_KEY);
609 |
610 | return editor.commit();
611 | }
612 |
613 | private SharedPreferences getSharedPreferences() {
614 | return applicationContext.getSharedPreferences(PreferencesConstants.FILE_NAME,
615 | Context.MODE_PRIVATE);
616 | }
617 |
618 | private List getCookieKeysFromPreferences() {
619 | SharedPreferences settings = getSharedPreferences();
620 | String cookieKeys = settings.getString(PreferencesConstants.COOKIES_KEY, "");
621 |
622 | return Arrays.asList(TextUtils.split(cookieKeys, PreferencesConstants.COOKIE_DELIMITER));
623 | }
624 |
625 | /**
626 | * Retrieves the refresh token from this {@code LiveAuthClient}'s
627 | * {@link Activity#getPreferences(int)}.
628 | *
629 | * @return the refresh token from persistent storage.
630 | */
631 | private String getRefreshTokenFromPreferences() {
632 | SharedPreferences settings = getSharedPreferences();
633 | return settings.getString(PreferencesConstants.REFRESH_TOKEN_KEY, null);
634 | }
635 | }
636 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/LiveAuthException.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * Indicates that an exception occurred during the Auth process.
27 | */
28 | public class LiveAuthException extends RuntimeException {
29 |
30 | private static final long serialVersionUID = 3368677530670470856L;
31 |
32 | private final String error;
33 | private final String errorUri;
34 |
35 |
36 | public LiveAuthException(String errorMessage) {
37 | super(errorMessage);
38 | this.error = "";
39 | this.errorUri = "";
40 | }
41 |
42 | public LiveAuthException(String errorMessage, Throwable throwable) {
43 | super(errorMessage, throwable);
44 | this.error = "";
45 | this.errorUri = "";
46 | }
47 |
48 | public LiveAuthException(String error, String errorDescription, String errorUri) {
49 | super(errorDescription);
50 |
51 | if (error == null) throw new AssertionError();
52 |
53 | this.error = error;
54 | this.errorUri = errorUri;
55 | }
56 |
57 | public LiveAuthException(String error, String errorDescription, String errorUri, Throwable cause) {
58 | super(errorDescription, cause);
59 |
60 | if (error == null) throw new AssertionError();
61 |
62 | this.error = error;
63 | this.errorUri = errorUri;
64 | }
65 |
66 | /**
67 | * @return Returns the authentication error.
68 | */
69 | public String getError() {
70 | return this.error;
71 | }
72 |
73 | /**
74 | * @return Returns the error URI.
75 | */
76 | public String getErrorUri() {
77 | return this.errorUri;
78 | }
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/LiveAuthListener.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * Handles callback methods for LiveAuthClient init, login, and logout methods.
27 | * Returns the * status of the operation when onAuthComplete is called. If there was an error
28 | * during the operation, onAuthError is called with the exception that was thrown.
29 | */
30 | public interface LiveAuthListener {
31 |
32 | /**
33 | * Invoked when the operation completes successfully.
34 | *
35 | * @param status The {@link LiveStatus} for an operation. If successful, the status is
36 | * CONNECTED. If unsuccessful, NOT_CONNECTED or UNKNOWN are returned.
37 | * @param session The {@link LiveConnectSession} from the {@link LiveAuthClient}.
38 | * @param userState An arbitrary object that is used to determine the caller of the method.
39 | */
40 | public void onAuthComplete(LiveStatus status, LiveConnectSession session, Object userState);
41 |
42 | /**
43 | * Invoked when the method call fails.
44 | *
45 | * @param exception The {@link LiveAuthException} error.
46 | * @param userState An arbitrary object that is used to determine the caller of the method.
47 | */
48 | public void onAuthError(LiveAuthException exception, Object userState);
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/LiveConnectSession.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import java.beans.PropertyChangeListener;
26 | import java.beans.PropertyChangeSupport;
27 | import java.util.Arrays;
28 | import java.util.Calendar;
29 | import java.util.Collections;
30 | import java.util.Date;
31 | import java.util.HashSet;
32 | import java.util.Set;
33 |
34 | /**
35 | * Represents a Live Connect session.
36 | */
37 | public class LiveConnectSession {
38 |
39 | private String accessToken;
40 | private String authenticationToken;
41 |
42 | /** Keeps track of all the listeners, and fires the property change events */
43 | private final PropertyChangeSupport changeSupport;
44 |
45 | /**
46 | * The LiveAuthClient that created this object.
47 | * This is needed in order to perform a refresh request.
48 | * There is a one-to-one relationship between the LiveConnectSession and LiveAuthClient.
49 | */
50 | private final LiveAuthClient creator;
51 |
52 | private Date expiresIn;
53 | private String refreshToken;
54 | private Set scopes;
55 | private String tokenType;
56 |
57 | /**
58 | * Constructors a new LiveConnectSession, and sets its creator to the passed in
59 | * LiveAuthClient. All other member variables are left uninitialized.
60 | *
61 | * @param creator
62 | */
63 | LiveConnectSession(LiveAuthClient creator) {
64 | if (creator == null) throw new AssertionError();
65 |
66 | this.creator = creator;
67 | this.changeSupport = new PropertyChangeSupport(this);
68 | }
69 |
70 | /**
71 | * Adds a {@link PropertyChangeListener} to the session that receives notification when any
72 | * property is changed.
73 | *
74 | * @param listener
75 | */
76 | public void addPropertyChangeListener(PropertyChangeListener listener) {
77 | if (listener == null) {
78 | return;
79 | }
80 |
81 | this.changeSupport.addPropertyChangeListener(listener);
82 | }
83 |
84 | /**
85 | * Adds a {@link PropertyChangeListener} to the session that receives notification when a
86 | * specific property is changed.
87 | *
88 | * @param propertyName
89 | * @param listener
90 | */
91 | public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
92 | if (listener == null) {
93 | return;
94 | }
95 |
96 | this.changeSupport.addPropertyChangeListener(propertyName, listener);
97 | }
98 |
99 | /**
100 | * @return The access token for the signed-in, connected user.
101 | */
102 | public String getAccessToken() {
103 | return this.accessToken;
104 | }
105 |
106 | /**
107 | * @return A user-specific token that provides information to an app so that it can validate
108 | * the user.
109 | */
110 | public String getAuthenticationToken() {
111 | return this.authenticationToken;
112 | }
113 |
114 | /**
115 | * @return The exact time when a session expires.
116 | */
117 | public Date getExpiresIn() {
118 | // Defensive copy
119 | return new Date(this.expiresIn.getTime());
120 | }
121 |
122 | /**
123 | * @return An array of all PropertyChangeListeners for this session.
124 | */
125 | public PropertyChangeListener[] getPropertyChangeListeners() {
126 | return this.changeSupport.getPropertyChangeListeners();
127 | }
128 |
129 | /**
130 | * @param propertyName
131 | * @return An array of all PropertyChangeListeners for a specific property for this session.
132 | */
133 | public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
134 | return this.changeSupport.getPropertyChangeListeners(propertyName);
135 | }
136 |
137 | /**
138 | * @return A user-specific refresh token that the app can use to refresh the access token.
139 | */
140 | public String getRefreshToken() {
141 | return this.refreshToken;
142 | }
143 |
144 | /**
145 | * @return The scopes that the user has consented to.
146 | */
147 | public Iterable getScopes() {
148 | // Defensive copy is not necessary, because this.scopes is an unmodifiableSet
149 | return this.scopes;
150 | }
151 |
152 | /**
153 | * @return The type of token.
154 | */
155 | public String getTokenType() {
156 | return this.tokenType;
157 | }
158 |
159 | /**
160 | * @return {@code true} if the session is expired.
161 | */
162 | public boolean isExpired() {
163 | if (this.expiresIn == null) {
164 | return true;
165 | }
166 |
167 | final Date now = new Date();
168 |
169 | return now.after(this.expiresIn);
170 | }
171 |
172 | /**
173 | * Removes a PropertyChangeListeners on a session.
174 | * @param listener
175 | */
176 | public void removePropertyChangeListener(PropertyChangeListener listener) {
177 | if (listener == null) {
178 | return;
179 | }
180 |
181 | this.changeSupport.removePropertyChangeListener(listener);
182 | }
183 |
184 | /**
185 | * Removes a PropertyChangeListener for a specific property on a session.
186 | * @param propertyName
187 | * @param listener
188 | */
189 | public void removePropertyChangeListener(String propertyName,
190 | PropertyChangeListener listener) {
191 | if (listener == null) {
192 | return;
193 | }
194 |
195 | this.changeSupport.removePropertyChangeListener(propertyName, listener);
196 | }
197 |
198 | @Override
199 | public String toString() {
200 | return String.format("LiveConnectSession [accessToken=%s, authenticationToken=%s, expiresIn=%s, refreshToken=%s, scopes=%s, tokenType=%s]",
201 | this.accessToken,
202 | this.authenticationToken,
203 | this.expiresIn,
204 | this.refreshToken,
205 | this.scopes,
206 | this.tokenType);
207 | }
208 |
209 | boolean contains(Iterable scopes) {
210 | if (scopes == null) {
211 | return true;
212 | } else if (this.scopes == null) {
213 | return false;
214 | }
215 |
216 | for (String scope : scopes) {
217 | if (!this.scopes.contains(scope)) {
218 | return false;
219 | }
220 | }
221 |
222 | return true;
223 | }
224 |
225 | /**
226 | * Fills in the LiveConnectSession with the OAuthResponse.
227 | * WARNING: The OAuthResponse must not contain OAuth.ERROR.
228 | *
229 | * @param response to load from
230 | */
231 | void loadFromOAuthResponse(OAuthSuccessfulResponse response) {
232 | this.accessToken = response.getAccessToken();
233 | this.tokenType = response.getTokenType().toString().toLowerCase();
234 |
235 | if (response.hasAuthenticationToken()) {
236 | this.authenticationToken = response.getAuthenticationToken();
237 | }
238 |
239 | if (response.hasExpiresIn()) {
240 | final Calendar calendar = Calendar.getInstance();
241 | calendar.add(Calendar.SECOND, response.getExpiresIn());
242 | this.setExpiresIn(calendar.getTime());
243 | }
244 |
245 | if (response.hasRefreshToken()) {
246 | this.refreshToken = response.getRefreshToken();
247 | }
248 |
249 | if (response.hasScope()) {
250 | final String scopeString = response.getScope();
251 | this.setScopes(Arrays.asList(scopeString.split(OAuth.SCOPE_DELIMITER)));
252 | }
253 | }
254 |
255 | /**
256 | * Refreshes this LiveConnectSession
257 | *
258 | * @return true if it was able to refresh the refresh token.
259 | */
260 | boolean refresh() {
261 | return this.creator.tryRefresh(this.getScopes());
262 | }
263 |
264 | void setAccessToken(String accessToken) {
265 | final String oldValue = this.accessToken;
266 | this.accessToken = accessToken;
267 |
268 | this.changeSupport.firePropertyChange("accessToken", oldValue, this.accessToken);
269 | }
270 |
271 | void setAuthenticationToken(String authenticationToken) {
272 | final String oldValue = this.authenticationToken;
273 | this.authenticationToken = authenticationToken;
274 |
275 | this.changeSupport.firePropertyChange("authenticationToken",
276 | oldValue,
277 | this.authenticationToken);
278 | }
279 |
280 | void setExpiresIn(Date expiresIn) {
281 | final Date oldValue = this.expiresIn;
282 | this.expiresIn = new Date(expiresIn.getTime());
283 |
284 | this.changeSupport.firePropertyChange("expiresIn", oldValue, this.expiresIn);
285 | }
286 |
287 | void setRefreshToken(String refreshToken) {
288 | final String oldValue = this.refreshToken;
289 | this.refreshToken = refreshToken;
290 |
291 | this.changeSupport.firePropertyChange("refreshToken", oldValue, this.refreshToken);
292 | }
293 |
294 | void setScopes(Iterable scopes) {
295 | final Iterable oldValue = this.scopes;
296 |
297 | // Defensive copy
298 | this.scopes = new HashSet();
299 | if (scopes != null) {
300 | for (String scope : scopes) {
301 | this.scopes.add(scope);
302 | }
303 | }
304 |
305 | this.scopes = Collections.unmodifiableSet(this.scopes);
306 |
307 | this.changeSupport.firePropertyChange("scopes", oldValue, this.scopes);
308 | }
309 |
310 | void setTokenType(String tokenType) {
311 | final String oldValue = this.tokenType;
312 | this.tokenType = tokenType;
313 |
314 | this.changeSupport.firePropertyChange("tokenType", oldValue, this.tokenType);
315 | }
316 |
317 | boolean willExpireInSecs(int secs) {
318 | final Calendar calendar = Calendar.getInstance();
319 | calendar.add(Calendar.SECOND, secs);
320 |
321 | final Date future = calendar.getTime();
322 |
323 | // if add secs seconds to the current time and it is after the expired time
324 | // then it is almost expired.
325 | return future.after(this.expiresIn);
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/LiveConnectUtils.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 |
26 | import android.text.TextUtils;
27 |
28 | /**
29 | * LiveConnectUtils is a non-instantiable utility class that contains various helper
30 | * methods and constants.
31 | */
32 | final class LiveConnectUtils {
33 |
34 | /**
35 | * Checks to see if the passed in Object is null, and throws a
36 | * NullPointerException if it is.
37 | *
38 | * @param object to check
39 | * @param parameterName name of the parameter that is used in the exception message
40 | * @throws NullPointerException if the Object is null
41 | */
42 | public static void assertNotNull(Object object, String parameterName) {
43 | if (TextUtils.isEmpty(parameterName)) throw new AssertionError();
44 |
45 | if (object == null) {
46 | final String message = String.format(ErrorMessages.NULL_PARAMETER, parameterName);
47 | throw new NullPointerException(message);
48 | }
49 | }
50 |
51 | /**
52 | * Checks to see if the passed in is an empty string, and throws an
53 | * IllegalArgumentException if it is.
54 | *
55 | * @param parameter to check
56 | * @param parameterName name of the parameter that is used in the exception message
57 | * @throws IllegalArgumentException if the parameter is empty
58 | * @throws NullPointerException if the String is null
59 | */
60 | public static void assertNotNullOrEmpty(String parameter, String parameterName) {
61 | if (TextUtils.isEmpty(parameterName)) throw new AssertionError();
62 |
63 | assertNotNull(parameter, parameterName);
64 |
65 | if (TextUtils.isEmpty(parameter)) {
66 | final String message = String.format(ErrorMessages.EMPTY_PARAMETER, parameterName);
67 | throw new IllegalArgumentException(message);
68 | }
69 | }
70 |
71 | /**
72 | * Private to prevent instantiation
73 | */
74 | private LiveConnectUtils() { throw new AssertionError(ErrorMessages.NON_INSTANTIABLE_CLASS); }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/LiveOperationException.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * Represents errors that occur when making requests to the Representational State Transfer
27 | * (REST) API.
28 | */
29 | public class LiveOperationException extends Exception {
30 |
31 | private static final long serialVersionUID = 4630383031651156731L;
32 |
33 | LiveOperationException(String message) {
34 | super(message);
35 | }
36 |
37 | LiveOperationException(String message, Throwable e) {
38 | super(message, e);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/LiveStatus.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * Specifies the status of an auth operation.
27 | */
28 | public enum LiveStatus {
29 | /** The status is not known. */
30 | UNKNOWN,
31 |
32 | /** The session is connected. */
33 | CONNECTED,
34 |
35 | /** The user has not consented to the application. */
36 | NOT_CONNECTED;
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/MicrosoftOAuthConfig.java:
--------------------------------------------------------------------------------
1 | package com.microsoft.services.msa;
2 |
3 | import android.net.Uri;
4 |
5 | /**
6 | * OAuth configuration for the Microsoft services
7 | */
8 | public class MicrosoftOAuthConfig implements OAuthConfig {
9 |
10 | /**
11 | * The domain to authenticate against
12 | */
13 | public static final String HTTPS_LOGIN_LIVE_COM = "https://login.live.com/";
14 |
15 | /**
16 | * Singleton instance
17 | */
18 | private static MicrosoftOAuthConfig sInstance;
19 |
20 | /**
21 | * The authorization uri
22 | */
23 | private Uri mOAuthAuthorizeUri;
24 |
25 | /**
26 | * The desktop uri
27 | */
28 | private Uri mOAuthDesktopUri;
29 |
30 | /**
31 | * The logout uri
32 | */
33 | private Uri mOAuthLogoutUri;
34 |
35 | /**
36 | * The auth token uri
37 | */
38 | private Uri mOAuthTokenUri;
39 |
40 | /**
41 | * Default Constructor
42 | */
43 | public MicrosoftOAuthConfig() {
44 | mOAuthAuthorizeUri = Uri.parse(HTTPS_LOGIN_LIVE_COM + "oauth20_authorize.srf");
45 | mOAuthDesktopUri = Uri.parse(HTTPS_LOGIN_LIVE_COM + "oauth20_desktop.srf");
46 | mOAuthLogoutUri = Uri.parse(HTTPS_LOGIN_LIVE_COM + "oauth20_logout.srf");
47 | mOAuthTokenUri = Uri.parse(HTTPS_LOGIN_LIVE_COM + "oauth20_token.srf");
48 | }
49 |
50 | /**
51 | * Gets an instance of the OneDrive OAuth configuration
52 | * @return The singleton instance
53 | */
54 | public static MicrosoftOAuthConfig getInstance() {
55 | if (MicrosoftOAuthConfig.sInstance == null) {
56 | MicrosoftOAuthConfig.sInstance = new MicrosoftOAuthConfig();
57 | }
58 |
59 | return MicrosoftOAuthConfig.sInstance;
60 | }
61 |
62 | @Override
63 | public Uri getAuthorizeUri() {
64 | return mOAuthAuthorizeUri;
65 | }
66 |
67 | @Override
68 | public Uri getDesktopUri() {
69 | return mOAuthDesktopUri;
70 | }
71 |
72 | @Override
73 | public Uri getLogoutUri() {
74 | return mOAuthLogoutUri;
75 | }
76 |
77 | @Override
78 | public Uri getTokenUri() {
79 | return mOAuthTokenUri;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/OAuth.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * OAuth is a non-instantiable utility class that contains types and constants
27 | * for the OAuth protocol.
28 | *
29 | * See the OAuth 2.0 spec
30 | * for more information.
31 | */
32 | final class OAuth {
33 |
34 | public enum DisplayType {
35 | ANDROID_PHONE,
36 | ANDROID_TABLET
37 | }
38 |
39 | public enum ErrorType {
40 | /**
41 | * Client authentication failed (e.g. unknown client, no
42 | * client authentication included, or unsupported
43 | * authentication method). The authorization server MAY
44 | * return an HTTP 401 (Unauthorized) status code to indicate
45 | * which HTTP authentication schemes are supported. If the
46 | * client attempted to authenticate via the "Authorization"
47 | * request header field, the authorization server MUST
48 | * respond with an HTTP 401 (Unauthorized) status code, and
49 | * include the "WWW-Authenticate" response header field
50 | * matching the authentication scheme used by the client.
51 | */
52 | INVALID_CLIENT,
53 |
54 | /**
55 | * The provided authorization grant (e.g. authorization
56 | * code, resource owner credentials, client credentials) is
57 | * invalid, expired, revoked, does not match the redirection
58 | * URI used in the authorization request, or was issued to
59 | * another client.
60 | */
61 | INVALID_GRANT,
62 |
63 | /**
64 | * The request is missing a required parameter, includes an
65 | * unsupported parameter value, repeats a parameter,
66 | * includes multiple credentials, utilizes more than one
67 | * mechanism for authenticating the client, or is otherwise
68 | * malformed.
69 | */
70 | INVALID_REQUEST,
71 |
72 | /**
73 | * The requested scope is invalid, unknown, malformed, or
74 | * exceeds the scope granted by the resource owner.
75 | */
76 | INVALID_SCOPE,
77 |
78 | /**
79 | * The authenticated client is not authorized to use this
80 | * authorization grant type.
81 | */
82 | UNAUTHORIZED_CLIENT,
83 |
84 | /**
85 | * The authorization grant type is not supported by the
86 | * authorization server.
87 | */
88 | UNSUPPORTED_GRANT_TYPE;
89 | }
90 |
91 | public enum GrantType {
92 | AUTHORIZATION_CODE,
93 | CLIENT_CREDENTIALS,
94 | PASSWORD,
95 | REFRESH_TOKEN;
96 | }
97 |
98 | public enum ResponseType {
99 | CODE,
100 | TOKEN;
101 | }
102 |
103 | public enum TokenType {
104 | BEARER
105 | }
106 |
107 | /**
108 | * Key for the access_token parameter.
109 | *
110 | * See Section 5.1
111 | * of the OAuth 2.0 spec for more information.
112 | */
113 | public static final String ACCESS_TOKEN = "access_token";
114 |
115 | /** The app's authentication token. */
116 | public static final String AUTHENTICATION_TOKEN = "authentication_token";
117 |
118 | /** The app's client ID. */
119 | public static final String CLIENT_ID = "client_id";
120 |
121 | /** Equivalent to the profile that is described in the OAuth 2.0 protocol spec. */
122 | public static final String CODE = "code";
123 |
124 | /**
125 | * The display type to be used for the authorization page. Valid values are
126 | * "popup", "touch", "page", or "none".
127 | */
128 | public static final String DISPLAY = "display";
129 |
130 | /**
131 | * Key for the error parameter.
132 | *
133 | * error can have the following values:
134 | * invalid_request, unauthorized_client, access_denied, unsupported_response_type,
135 | * invalid_scope, server_error, or temporarily_unavailable.
136 | */
137 | public static final String ERROR = "error";
138 |
139 | /**
140 | * Key for the error_description parameter. error_description is described below.
141 | *
142 | * OPTIONAL. A human-readable UTF-8 encoded text providing
143 | * additional information, used to assist the client developer in
144 | * understanding the error that occurred.
145 | */
146 | public static final String ERROR_DESCRIPTION = "error_description";
147 |
148 | /**
149 | * Key for the error_uri parameter. error_uri is described below.
150 | *
151 | * OPTIONAL. A URI identifying a human-readable web page with
152 | * information about the error, used to provide the client
153 | * developer with additional information about the error.
154 | */
155 | public static final String ERROR_URI = "error_uri";
156 |
157 | /**
158 | * Key for the expires_in parameter. expires_in is described below.
159 | *
160 | * OPTIONAL. The lifetime in seconds of the access token. For
161 | * example, the value "3600" denotes that the access token will
162 | * expire in one hour from the time the response was generated.
163 | */
164 | public static final String EXPIRES_IN = "expires_in";
165 |
166 | /**
167 | * Key for the grant_type parameter. grant_type is described below.
168 | *
169 | * grant_type is used in a token request. It can take on the following
170 | * values: authorization_code, password, client_credentials, or refresh_token.
171 | */
172 | public static final String GRANT_TYPE = "grant_type";
173 |
174 | /**
175 | * Optional. A market string that determines how the consent user interface
176 | * (UI) is localized. If the value of this parameter is missing or is not
177 | * valid, a market value is determined by using an internal algorithm.
178 | */
179 | public static final String LOCALE = "locale";
180 |
181 | /**
182 | * Key for the redirect_uri parameter.
183 | *
184 | * See Section 3.1.2
185 | * of the OAuth 2.0 spec for more information.
186 | */
187 | public static final String REDIRECT_URI = "redirect_uri";
188 |
189 | /**
190 | * Key used for the refresh_token parameter.
191 | *
192 | * See Section 5.1
193 | * of the OAuth 2.0 spec for more information.
194 | */
195 | public static final String REFRESH_TOKEN = "refresh_token";
196 |
197 | /**
198 | * The type of data to be returned in the response from the authorization
199 | * server. Valid values are "code" or "token".
200 | */
201 | public static final String RESPONSE_TYPE = "response_type";
202 |
203 | /**
204 | * Equivalent to the scope parameter that is described in the OAuth 2.0
205 | * protocol spec.
206 | */
207 | public static final String SCOPE = "scope";
208 |
209 | /** Delimiter for the scopes field response. */
210 | public static final String SCOPE_DELIMITER = " ";
211 |
212 | /**
213 | * Equivalent to the state parameter that is described in the OAuth 2.0
214 | * protocol spec.
215 | */
216 | public static final String STATE = "state";
217 |
218 | /**
219 | * The login hint for the username to be pre-filled out in the web form
220 | */
221 | public static final String LOGIN_HINT = "login_hint";
222 |
223 | /**
224 | * The alternative login hint for the username to be pre-filled out in the web form
225 | */
226 | public static final String USER_NAME = "username";
227 |
228 | public static final String THEME = "theme";
229 |
230 | /**
231 | * Key used for the token_type parameter.
232 | *
233 | * See Section 5.1
234 | * of the OAuth 2.0 spec for more information.
235 | */
236 | public static final String TOKEN_TYPE = "token_type";
237 |
238 | /** Private to prevent instantiation */
239 | private OAuth() { throw new AssertionError(ErrorMessages.NON_INSTANTIABLE_CLASS); }
240 | }
241 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/OAuthConfig.java:
--------------------------------------------------------------------------------
1 | package com.microsoft.services.msa;
2 |
3 | import android.net.Uri;
4 |
5 | /**
6 | * OAuth endpoint configuration.
7 | */
8 | public interface OAuthConfig {
9 |
10 | /**
11 | * The authorization uri
12 | * @return the value
13 | */
14 | Uri getAuthorizeUri();
15 |
16 | /**
17 | * The desktop uri
18 | * @return the value
19 | */
20 | Uri getDesktopUri();
21 |
22 | /**
23 | * The logout uri
24 | * @return the value
25 | */
26 | Uri getLogoutUri();
27 |
28 | /**
29 | * The auth token uri
30 | * @return the value
31 | */
32 | Uri getTokenUri();
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/OAuthErrorResponse.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import java.util.Locale;
26 |
27 | import org.json.JSONException;
28 | import org.json.JSONObject;
29 |
30 |
31 | /**
32 | * OAuthErrorResponse represents the an Error Response from the OAuth server.
33 | */
34 | class OAuthErrorResponse implements OAuthResponse {
35 |
36 | /**
37 | * Builder is a helper class to create a OAuthErrorResponse.
38 | * An OAuthResponse must contain an error, but an error_description and
39 | * error_uri are optional
40 | */
41 | public static class Builder {
42 | private final OAuth.ErrorType error;
43 | private String errorDescription;
44 | private String errorUri;
45 |
46 | public Builder(OAuth.ErrorType error) {
47 | if (error == null) throw new AssertionError();
48 |
49 | this.error = error;
50 | }
51 |
52 | /**
53 | * @return a new instance of an OAuthErrorResponse containing
54 | * the values called on the builder.
55 | */
56 | public OAuthErrorResponse build() {
57 | return new OAuthErrorResponse(this);
58 | }
59 |
60 | public Builder errorDescription(String errorDescription) {
61 | this.errorDescription = errorDescription;
62 | return this;
63 | }
64 |
65 | public Builder errorUri(String errorUri) {
66 | this.errorUri = errorUri;
67 | return this;
68 | }
69 | }
70 |
71 | /**
72 | * Static constructor that creates an OAuthErrorResponse from the given OAuth server's
73 | * JSONObject response
74 | * @param response from the OAuth server
75 | * @return A new instance of an OAuthErrorResponse from the given response
76 | * @throws LiveAuthException if there is an JSONException, or the error type cannot be found.
77 | */
78 | public static OAuthErrorResponse createFromJson(JSONObject response) throws LiveAuthException {
79 | final String errorString;
80 | try {
81 | errorString = response.getString(OAuth.ERROR);
82 | } catch (JSONException e) {
83 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
84 | }
85 |
86 | final OAuth.ErrorType error;
87 | try {
88 | error = OAuth.ErrorType.valueOf(errorString.toUpperCase());
89 | } catch (IllegalArgumentException e) {
90 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
91 | } catch (NullPointerException e) {
92 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
93 | }
94 |
95 | final Builder builder = new Builder(error);
96 | if (response.has(OAuth.ERROR_DESCRIPTION)) {
97 | final String errorDescription;
98 | try {
99 | errorDescription = response.getString(OAuth.ERROR_DESCRIPTION);
100 | } catch (JSONException e) {
101 | throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
102 | }
103 | builder.errorDescription(errorDescription);
104 | }
105 |
106 | if (response.has(OAuth.ERROR_URI)) {
107 | final String errorUri;
108 | try {
109 | errorUri = response.getString(OAuth.ERROR_URI);
110 | } catch (JSONException e) {
111 | throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
112 | }
113 | builder.errorUri(errorUri);
114 | }
115 |
116 | return builder.build();
117 | }
118 |
119 | /**
120 | * @param response to check
121 | * @return true if the given JSONObject is a valid OAuth response
122 | */
123 | public static boolean validOAuthErrorResponse(JSONObject response) {
124 | return response.has(OAuth.ERROR);
125 | }
126 |
127 | /** REQUIRED. */
128 | private final OAuth.ErrorType error;
129 |
130 | /**
131 | * OPTIONAL. A human-readable UTF-8 encoded text providing
132 | * additional information, used to assist the client developer in
133 | * understanding the error that occurred.
134 | */
135 | private final String errorDescription;
136 |
137 | /**
138 | * OPTIONAL. A URI identifying a human-readable web page with
139 | * information about the error, used to provide the client
140 | * developer with additional information about the error.
141 | */
142 | private final String errorUri;
143 |
144 | /**
145 | * OAuthErrorResponse constructor. It is private to enforce
146 | * the use of the Builder.
147 | *
148 | * @param builder to use to construct the object.
149 | */
150 | private OAuthErrorResponse(Builder builder) {
151 | this.error = builder.error;
152 | this.errorDescription = builder.errorDescription;
153 | this.errorUri = builder.errorUri;
154 | }
155 |
156 | @Override
157 | public void accept(OAuthResponseVisitor visitor) {
158 | visitor.visit(this);
159 | }
160 |
161 | /**
162 | * error is a required field.
163 | * @return the error
164 | */
165 | public OAuth.ErrorType getError() {
166 | return error;
167 | }
168 |
169 | /**
170 | * error_description is an optional field
171 | * @return error_description
172 | */
173 | public String getErrorDescription() {
174 | return errorDescription;
175 | }
176 |
177 | /**
178 | * error_uri is an optional field
179 | * @return error_uri
180 | */
181 | public String getErrorUri() {
182 | return errorUri;
183 | }
184 |
185 | @Override
186 | public String toString() {
187 | return String.format("OAuthErrorResponse [error=%s, errorDescription=%s, errorUri=%s]",
188 | error.toString().toLowerCase(Locale.US), errorDescription, errorUri);
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/OAuthRequestObserver.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * An observer of an OAuth Request. It will be notified of an Exception or of a Response.
27 | */
28 | interface OAuthRequestObserver {
29 | /**
30 | * Callback used on an exception.
31 | *
32 | * @param exception
33 | */
34 | public void onException(LiveAuthException exception);
35 |
36 | /**
37 | * Callback used on a response.
38 | *
39 | * @param response
40 | */
41 | public void onResponse(OAuthResponse response);
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/OAuthResponse.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * OAuthRespresent a response from an OAuth server.
27 | * Known implementors are OAuthSuccessfulResponse and OAuthErrorResponse.
28 | * Different OAuthResponses can be determined by using the OAuthResponseVisitor.
29 | */
30 | interface OAuthResponse {
31 |
32 | /**
33 | * Calls visit() on the visitor.
34 | * This method is used to determine which OAuthResponse is being returned
35 | * without using instance of.
36 | *
37 | * @param visitor to visit the given OAuthResponse
38 | */
39 | public void accept(OAuthResponseVisitor visitor);
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/OAuthResponseVisitor.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * OAuthResponseVisitor is used to visit various OAuthResponse.
27 | */
28 | interface OAuthResponseVisitor {
29 |
30 | /**
31 | * Called when an OAuthSuccessfulResponse is visited.
32 | *
33 | * @param response being visited
34 | */
35 | public void visit(OAuthSuccessfulResponse response);
36 |
37 | /**
38 | * Called when an OAuthErrorResponse is being visited.
39 | *
40 | * @param response being visited
41 | */
42 | public void visit(OAuthErrorResponse response);
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/OAuthSuccessfulResponse.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import java.util.Map;
26 |
27 | import org.json.JSONException;
28 | import org.json.JSONObject;
29 |
30 | import android.text.TextUtils;
31 |
32 |
33 | /**
34 | * OAuthSuccessfulResponse represents a successful response form an OAuth server.
35 | */
36 | class OAuthSuccessfulResponse implements OAuthResponse {
37 |
38 | /**
39 | * Builder is a utility class that is used to build a new OAuthSuccessfulResponse.
40 | * It must be constructed with the required fields, and can add on the optional ones.
41 | */
42 | public static class Builder {
43 | private final String accessToken;
44 | private String authenticationToken;
45 | private int expiresIn = UNINITIALIZED;
46 | private String refreshToken;
47 | private String scope;
48 | private final OAuth.TokenType tokenType;
49 |
50 | public Builder(String accessToken, OAuth.TokenType tokenType) {
51 | if (accessToken == null) throw new AssertionError();
52 | if (TextUtils.isEmpty(accessToken)) throw new AssertionError();
53 | if (tokenType == null) throw new AssertionError();
54 |
55 | this.accessToken = accessToken;
56 | this.tokenType = tokenType;
57 | }
58 |
59 | public Builder authenticationToken(String authenticationToken) {
60 | this.authenticationToken = authenticationToken;
61 | return this;
62 | }
63 |
64 | /**
65 | * @return a new instance of an OAuthSuccessfulResponse with the given
66 | * parameters passed into the builder.
67 | */
68 | public OAuthSuccessfulResponse build() {
69 | return new OAuthSuccessfulResponse(this);
70 | }
71 |
72 | public Builder expiresIn(int expiresIn) {
73 | this.expiresIn = expiresIn;
74 | return this;
75 | }
76 |
77 | public Builder refreshToken(String refreshToken) {
78 | this.refreshToken = refreshToken;
79 | return this;
80 | }
81 |
82 | public Builder scope(String scope) {
83 | this.scope = scope;
84 | return this;
85 | }
86 | }
87 |
88 | /** Used to declare expiresIn uninitialized */
89 | private static final int UNINITIALIZED = -1;
90 |
91 | public static OAuthSuccessfulResponse createFromFragment(
92 | Map fragmentParameters) throws LiveAuthException {
93 | String accessToken = fragmentParameters.get(OAuth.ACCESS_TOKEN);
94 | String tokenTypeString = fragmentParameters.get(OAuth.TOKEN_TYPE);
95 |
96 | // must have accessToken and tokenTypeString to be a valid OAuthSuccessfulResponse
97 | if (accessToken == null) throw new AssertionError();
98 | if (tokenTypeString == null) throw new AssertionError();
99 |
100 | OAuth.TokenType tokenType;
101 | try {
102 | tokenType = OAuth.TokenType.valueOf(tokenTypeString.toUpperCase());
103 | } catch (IllegalArgumentException e) {
104 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
105 | }
106 |
107 | Builder builder =
108 | new Builder(accessToken, tokenType);
109 |
110 | String authenticationToken = fragmentParameters.get(OAuth.AUTHENTICATION_TOKEN);
111 | if (authenticationToken != null) {
112 | builder.authenticationToken(authenticationToken);
113 | }
114 |
115 | String expiresInString = fragmentParameters.get(OAuth.EXPIRES_IN);
116 | if (expiresInString != null) {
117 | final int expiresIn;
118 | try {
119 | expiresIn = Integer.parseInt(expiresInString);
120 | } catch (final NumberFormatException e) {
121 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
122 | }
123 |
124 | builder.expiresIn(expiresIn);
125 | }
126 |
127 | String scope = fragmentParameters.get(OAuth.SCOPE);
128 | if (scope != null) {
129 | builder.scope(scope);
130 | }
131 |
132 | return builder.build();
133 | }
134 |
135 | /**
136 | * Static constructor used to create a new OAuthSuccessfulResponse from an
137 | * OAuth server's JSON response.
138 | *
139 | * @param response from an OAuth server that is used to create the object.
140 | * @return a new instance of OAuthSuccessfulResponse that is created from the given JSONObject
141 | * @throws LiveAuthException if there is a JSONException or the token_type is unknown.
142 | */
143 | public static OAuthSuccessfulResponse createFromJson(JSONObject response)
144 | throws LiveAuthException {
145 | if (!validOAuthSuccessfulResponse(response)) throw new AssertionError();
146 |
147 | final String accessToken;
148 | try {
149 | accessToken = response.getString(OAuth.ACCESS_TOKEN);
150 | } catch (final JSONException e) {
151 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
152 | }
153 |
154 | final String tokenTypeString;
155 | try {
156 | tokenTypeString = response.getString(OAuth.TOKEN_TYPE);
157 | } catch (final JSONException e) {
158 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
159 | }
160 |
161 | final OAuth.TokenType tokenType;
162 | try {
163 | tokenType = OAuth.TokenType.valueOf(tokenTypeString.toUpperCase());
164 | } catch (final IllegalArgumentException e) {
165 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
166 | } catch (final NullPointerException e) {
167 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
168 | }
169 |
170 | final Builder builder = new Builder(accessToken, tokenType);
171 |
172 | if (response.has(OAuth.AUTHENTICATION_TOKEN)) {
173 | final String authenticationToken;
174 | try {
175 | authenticationToken = response.getString(OAuth.AUTHENTICATION_TOKEN);
176 | } catch (final JSONException e) {
177 | throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
178 | }
179 | builder.authenticationToken(authenticationToken);
180 | }
181 |
182 | if (response.has(OAuth.REFRESH_TOKEN)) {
183 | final String refreshToken;
184 | try {
185 | refreshToken = response.getString(OAuth.REFRESH_TOKEN);
186 | } catch (final JSONException e) {
187 | throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
188 | }
189 | builder.refreshToken(refreshToken);
190 | }
191 |
192 | if (response.has(OAuth.EXPIRES_IN)) {
193 | final int expiresIn;
194 | try {
195 | expiresIn = response.getInt(OAuth.EXPIRES_IN);
196 | } catch (final JSONException e) {
197 | throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
198 | }
199 | builder.expiresIn(expiresIn);
200 | }
201 |
202 | if (response.has(OAuth.SCOPE)) {
203 | final String scope;
204 | try {
205 | scope = response.getString(OAuth.SCOPE);
206 | } catch (final JSONException e) {
207 | throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
208 | }
209 | builder.scope(scope);
210 | }
211 |
212 | return builder.build();
213 | }
214 |
215 | /**
216 | * @param response
217 | * @return true if the given JSONObject has the required fields to construct an
218 | * OAuthSuccessfulResponse (i.e., has access_token and token_type)
219 | */
220 | public static boolean validOAuthSuccessfulResponse(JSONObject response) {
221 | return response.has(OAuth.ACCESS_TOKEN) &&
222 | response.has(OAuth.TOKEN_TYPE);
223 | }
224 |
225 | /** REQUIRED. The access token issued by the authorization server. */
226 | private final String accessToken;
227 |
228 | private final String authenticationToken;
229 |
230 | /**
231 | * OPTIONAL. The lifetime in seconds of the access token. For
232 | * example, the value "3600" denotes that the access token will
233 | * expire in one hour from the time the response was generated.
234 | */
235 | private final int expiresIn;
236 |
237 | /**
238 | * OPTIONAL. The refresh token which can be used to obtain new
239 | * access tokens using the same authorization grant.
240 | */
241 | private final String refreshToken;
242 |
243 | /** OPTIONAL. */
244 | private final String scope;
245 |
246 | /** REQUIRED. */
247 | private final OAuth.TokenType tokenType;
248 |
249 | /**
250 | * Private constructor to enforce the user of the builder.
251 | * @param builder to use to construct the object from.
252 | */
253 | private OAuthSuccessfulResponse(Builder builder) {
254 | this.accessToken = builder.accessToken;
255 | this.authenticationToken = builder.authenticationToken;
256 | this.tokenType = builder.tokenType;
257 | this.refreshToken = builder.refreshToken;
258 | this.expiresIn = builder.expiresIn;
259 | this.scope = builder.scope;
260 | }
261 |
262 | @Override
263 | public void accept(OAuthResponseVisitor visitor) {
264 | visitor.visit(this);
265 | }
266 |
267 | public String getAccessToken() {
268 | return this.accessToken;
269 | }
270 |
271 | public String getAuthenticationToken() {
272 | return this.authenticationToken;
273 | }
274 |
275 | public int getExpiresIn() {
276 | return this.expiresIn;
277 | }
278 |
279 | public String getRefreshToken() {
280 | return this.refreshToken;
281 | }
282 |
283 | public String getScope() {
284 | return this.scope;
285 | }
286 |
287 | public OAuth.TokenType getTokenType() {
288 | return this.tokenType;
289 | }
290 |
291 | public boolean hasAuthenticationToken() {
292 | return this.authenticationToken != null && !TextUtils.isEmpty(this.authenticationToken);
293 | }
294 |
295 | public boolean hasExpiresIn() {
296 | return this.expiresIn != UNINITIALIZED;
297 | }
298 |
299 | public boolean hasRefreshToken() {
300 | return this.refreshToken != null && !TextUtils.isEmpty(this.refreshToken);
301 | }
302 |
303 | public boolean hasScope() {
304 | return this.scope != null && !TextUtils.isEmpty(this.scope);
305 | }
306 |
307 | @Override
308 | public String toString() {
309 | return String.format("OAuthSuccessfulResponse [accessToken=%s, authenticationToken=%s, tokenType=%s, refreshToken=%s, expiresIn=%s, scope=%s]",
310 | this.accessToken,
311 | this.authenticationToken,
312 | this.tokenType,
313 | this.refreshToken,
314 | this.expiresIn,
315 | this.scope);
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/ObservableOAuthRequest.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * An OAuth Request that can be observed, by adding observers that will be notified on any
27 | * exception or response.
28 | */
29 | interface ObservableOAuthRequest {
30 | /**
31 | * Adds an observer to observe the OAuth request
32 | *
33 | * @param observer to add
34 | */
35 | public void addObserver(OAuthRequestObserver observer);
36 |
37 | /**
38 | * Removes an observer that is observing the OAuth request
39 | *
40 | * @param observer to remove
41 | * @return true if the observer was removed.
42 | */
43 | public boolean removeObserver(OAuthRequestObserver observer);
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/OverwriteOption.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * Enum that specifies what to do during a naming conflict during an upload.
27 | */
28 | public enum OverwriteOption {
29 |
30 | /** Overwrite the existing file. */
31 | Overwrite {
32 | @Override
33 | protected String overwriteQueryParamValue() {
34 | return "true";
35 | }
36 | },
37 |
38 | /** Do Not Overwrite the existing file and cancel the upload. */
39 | DoNotOverwrite {
40 | @Override
41 | protected String overwriteQueryParamValue() {
42 | return "false";
43 | }
44 | },
45 |
46 | /** Rename the current file to avoid a name conflict. */
47 | Rename {
48 | @Override
49 | protected String overwriteQueryParamValue() {
50 | return "choosenewname";
51 | }
52 | };
53 |
54 | /**
55 | * Leaves any existing overwrite query parameter on appends this overwrite
56 | * to the given UriBuilder.
57 | */
58 | void appendQueryParameterOnTo(UriBuilder uri) {
59 | uri.appendQueryParameter(QueryParameters.OVERWRITE, this.overwriteQueryParamValue());
60 | }
61 |
62 | abstract protected String overwriteQueryParamValue();
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/PreferencesConstants.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * Static class that holds constants used by an application's preferences.
27 | */
28 | final class PreferencesConstants {
29 | public static final String COOKIES_KEY = "cookies";
30 |
31 | /** Name of the preference file */
32 | public static final String FILE_NAME = "com.microsoft.live";
33 |
34 | public static final String REFRESH_TOKEN_KEY = "refresh_token";
35 | public static final String COOKIE_DELIMITER = ",";
36 |
37 | private PreferencesConstants() { throw new AssertionError(); }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/QueryParameters.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | /**
26 | * QueryParameters is a non-instantiable utility class that holds query parameter constants
27 | * used by the API service.
28 | */
29 | final class QueryParameters {
30 |
31 | public static final String PRETTY = "pretty";
32 | public static final String CALLBACK = "callback";
33 | public static final String SUPPRESS_REDIRECTS = "suppress_redirects";
34 | public static final String SUPPRESS_RESPONSE_CODES = "suppress_response_codes";
35 | public static final String METHOD = "method";
36 | public static final String OVERWRITE = "overwrite";
37 | public static final String RETURN_SSL_RESOURCES = "return_ssl_resources";
38 |
39 | /** Private to present instantiation. */
40 | private QueryParameters() {
41 | throw new AssertionError(ErrorMessages.NON_INSTANTIABLE_CLASS);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/RefreshAccessTokenRequest.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import java.util.List;
26 |
27 | import org.apache.http.NameValuePair;
28 | import org.apache.http.client.HttpClient;
29 | import org.apache.http.message.BasicNameValuePair;
30 |
31 | import android.text.TextUtils;
32 |
33 |
34 | /**
35 | * RefreshAccessTokenRequest performs a refresh access token request. Most of the work
36 | * is done by the parent class, TokenRequest. This class adds in the required body parameters via
37 | * TokenRequest's hook method, constructBody().
38 | */
39 | class RefreshAccessTokenRequest extends TokenRequest {
40 |
41 | /** REQUIRED. Value MUST be set to "refresh_token". */
42 | private final OAuth.GrantType grantType = OAuth.GrantType.REFRESH_TOKEN;
43 |
44 | /** REQUIRED. The refresh token issued to the client. */
45 | private final String refreshToken;
46 |
47 | private final String scope;
48 |
49 | public RefreshAccessTokenRequest(final HttpClient client,
50 | final String clientId,
51 | final String refreshToken,
52 | final String scope,
53 | final OAuthConfig oAuthConfig) {
54 | super(client, clientId, oAuthConfig);
55 |
56 | if (refreshToken == null) throw new AssertionError();
57 | if (TextUtils.isEmpty(refreshToken)) throw new AssertionError();
58 | if (scope == null) throw new AssertionError();
59 | if (TextUtils.isEmpty(scope)) throw new AssertionError();
60 |
61 | this.refreshToken = refreshToken;
62 | this.scope = scope;
63 | }
64 |
65 | @Override
66 | protected void constructBody(List body) {
67 | body.add(new BasicNameValuePair(OAuth.REFRESH_TOKEN, this.refreshToken));
68 | body.add(new BasicNameValuePair(OAuth.SCOPE, this.scope));
69 | body.add(new BasicNameValuePair(OAuth.GRANT_TYPE, this.grantType.toString()));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/ScreenSize.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import android.app.Activity;
26 | import android.content.res.Configuration;
27 | import android.util.Log;
28 |
29 | import com.microsoft.services.msa.DeviceType;
30 |
31 | /**
32 | * The ScreenSize is used to determine the DeviceType.
33 | * Small and Normal ScreenSizes are Phones.
34 | * Large and XLarge are Tablets.
35 | */
36 | enum ScreenSize {
37 | SMALL {
38 | @Override
39 | public DeviceType getDeviceType() {
40 | return DeviceType.PHONE;
41 | }
42 | },
43 | NORMAL {
44 | @Override
45 | public DeviceType getDeviceType() {
46 | return DeviceType.PHONE;
47 | }
48 |
49 | },
50 | LARGE {
51 | @Override
52 | public DeviceType getDeviceType() {
53 | return DeviceType.TABLET;
54 | }
55 | },
56 | XLARGE {
57 | @Override
58 | public DeviceType getDeviceType() {
59 | return DeviceType.TABLET;
60 | }
61 | };
62 |
63 | public abstract DeviceType getDeviceType();
64 |
65 | /**
66 | * Configuration.SCREENLAYOUT_SIZE_XLARGE was not provided in API level 9.
67 | * However, its value of 4 does show up.
68 | */
69 | private static final int SCREENLAYOUT_SIZE_XLARGE = 4;
70 |
71 | public static ScreenSize determineScreenSize(Activity activity) {
72 | int screenLayout = activity.getResources().getConfiguration().screenLayout;
73 | int screenLayoutMasked = screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK;
74 | switch (screenLayoutMasked) {
75 | case Configuration.SCREENLAYOUT_SIZE_SMALL:
76 | return SMALL;
77 | case Configuration.SCREENLAYOUT_SIZE_NORMAL:
78 | return NORMAL;
79 | case Configuration.SCREENLAYOUT_SIZE_LARGE:
80 | return LARGE;
81 | case SCREENLAYOUT_SIZE_XLARGE:
82 | return XLARGE;
83 | default:
84 | // If we cannot determine the ScreenSize, we'll guess and say it's normal.
85 | Log.d(
86 | "Live SDK ScreenSize",
87 | "Unable to determine ScreenSize. A Normal ScreenSize will be returned.");
88 | return NORMAL;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/TokenRequest.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import java.io.IOException;
26 | import java.io.UnsupportedEncodingException;
27 | import java.util.ArrayList;
28 | import java.util.List;
29 |
30 | import org.apache.http.HttpEntity;
31 | import org.apache.http.HttpResponse;
32 | import org.apache.http.NameValuePair;
33 | import org.apache.http.client.ClientProtocolException;
34 | import org.apache.http.client.HttpClient;
35 | import org.apache.http.client.entity.UrlEncodedFormEntity;
36 | import org.apache.http.client.methods.HttpPost;
37 | import org.apache.http.client.utils.URLEncodedUtils;
38 | import org.apache.http.message.BasicNameValuePair;
39 | import org.apache.http.protocol.HTTP;
40 | import org.apache.http.util.EntityUtils;
41 | import org.json.JSONException;
42 | import org.json.JSONObject;
43 |
44 | import android.net.Uri;
45 | import android.text.TextUtils;
46 |
47 | /**
48 | * Abstract class that represents an OAuth token request.
49 | * Known subclasses include AccessTokenRequest and RefreshAccessTokenRequest
50 | */
51 | abstract class TokenRequest {
52 |
53 | private static final String CONTENT_TYPE =
54 | URLEncodedUtils.CONTENT_TYPE + ";charset=" + HTTP.UTF_8;
55 |
56 | protected final HttpClient client;
57 | protected final String clientId;
58 | protected final OAuthConfig mOAuthConfig;
59 |
60 | /**
61 | * Constructs a new TokenRequest instance and initializes its parameters.
62 | *
63 | * @param client the HttpClient to make HTTP requests on
64 | * @param clientId the client_id of the calling application
65 | */
66 | public TokenRequest(HttpClient client, String clientId, final OAuthConfig oAuthConfig) {
67 | if (client == null) throw new AssertionError();
68 | if (clientId == null) throw new AssertionError();
69 | if (TextUtils.isEmpty(clientId)) throw new AssertionError();
70 |
71 | this.client = client;
72 | this.clientId = clientId;
73 | this.mOAuthConfig = oAuthConfig;
74 | }
75 |
76 | /**
77 | * Performs the Token Request and returns the OAuth server's response.
78 | *
79 | * @return The OAuthResponse from the server
80 | * @throws LiveAuthException if there is any exception while executing the request
81 | * (e.g., IOException, JSONException)
82 | */
83 | public OAuthResponse execute() throws LiveAuthException {
84 | final Uri requestUri = mOAuthConfig.getTokenUri();
85 |
86 | final HttpPost request = new HttpPost(requestUri.toString());
87 |
88 | final List body = new ArrayList();
89 | body.add(new BasicNameValuePair(OAuth.CLIENT_ID, this.clientId));
90 |
91 | // constructBody allows subclasses to add to body
92 | this.constructBody(body);
93 |
94 | try {
95 | final UrlEncodedFormEntity entity = new UrlEncodedFormEntity(body, HTTP.UTF_8);
96 | entity.setContentType(CONTENT_TYPE);
97 | request.setEntity(entity);
98 | } catch (UnsupportedEncodingException e) {
99 | throw new LiveAuthException(ErrorMessages.CLIENT_ERROR, e);
100 | }
101 |
102 | final HttpResponse response;
103 | try {
104 | response = this.client.execute(request);
105 | } catch (ClientProtocolException e) {
106 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
107 | } catch (IOException e) {
108 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
109 | }
110 |
111 | final HttpEntity entity = response.getEntity();
112 | final String stringResponse;
113 | try {
114 | stringResponse = EntityUtils.toString(entity);
115 | } catch (IOException e) {
116 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
117 | }
118 |
119 | final JSONObject jsonResponse;
120 | try {
121 | jsonResponse = new JSONObject(stringResponse);
122 | } catch (JSONException e) {
123 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR, e);
124 | }
125 |
126 | if (OAuthErrorResponse.validOAuthErrorResponse(jsonResponse)) {
127 | return OAuthErrorResponse.createFromJson(jsonResponse);
128 | } else if (OAuthSuccessfulResponse.validOAuthSuccessfulResponse(jsonResponse)) {
129 | return OAuthSuccessfulResponse.createFromJson(jsonResponse);
130 | } else {
131 | throw new LiveAuthException(ErrorMessages.SERVER_ERROR);
132 | }
133 | }
134 |
135 | /**
136 | * This method gives a hook in the execute process, and allows subclasses
137 | * to add to the HttpRequest's body.
138 | * NOTE: The content type has already been added
139 | *
140 | * @param body of NameValuePairs to add to
141 | */
142 | protected abstract void constructBody(List body);
143 | }
144 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/TokenRequestAsync.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import android.os.AsyncTask;
26 |
27 | /**
28 | * TokenRequestAsync performs an async token request. It takes in a TokenRequest,
29 | * executes it, checks the OAuthResponse, and then calls the given listener.
30 | */
31 | class TokenRequestAsync extends AsyncTask implements ObservableOAuthRequest {
32 |
33 | private final DefaultObservableOAuthRequest observerable;
34 |
35 | /** Not null if there was an exception */
36 | private LiveAuthException exception;
37 |
38 | /** Not null if there was a response */
39 | private OAuthResponse response;
40 |
41 | private final TokenRequest request;
42 |
43 | /**
44 | * Constructs a new TokenRequestAsync and initializes its member variables
45 | *
46 | * @param request to perform
47 | */
48 | public TokenRequestAsync(TokenRequest request) {
49 | if (request == null) throw new AssertionError();
50 |
51 | this.observerable = new DefaultObservableOAuthRequest();
52 | this.request = request;
53 | }
54 |
55 | @Override
56 | public void addObserver(OAuthRequestObserver observer) {
57 | this.observerable.addObserver(observer);
58 | }
59 |
60 | @Override
61 | public boolean removeObserver(OAuthRequestObserver observer) {
62 | return this.observerable.removeObserver(observer);
63 | }
64 |
65 | @Override
66 | protected Void doInBackground(Void... params) {
67 | try {
68 | this.response = this.request.execute();
69 | } catch (LiveAuthException e) {
70 | this.exception = e;
71 | }
72 |
73 | return null;
74 | }
75 |
76 | @Override
77 | protected void onPostExecute(Void result) {
78 | super.onPostExecute(result);
79 |
80 | if (this.response != null) {
81 | this.observerable.notifyObservers(this.response);
82 | } else if (this.exception != null) {
83 | this.observerable.notifyObservers(this.exception);
84 | } else {
85 | final LiveAuthException exception = new LiveAuthException(ErrorMessages.CLIENT_ERROR);
86 | this.observerable.notifyObservers(exception);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/microsoft/services/msa/UriBuilder.java:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | // Copyright (c) 2014 Microsoft Corporation
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | // ------------------------------------------------------------------------------
22 |
23 | package com.microsoft.services.msa;
24 |
25 | import java.util.Iterator;
26 | import java.util.LinkedList;
27 |
28 | import android.net.Uri;
29 | import android.text.TextUtils;
30 | import android.util.Log;
31 |
32 | /**
33 | * Class for building URIs. The most useful benefit of this class is its query parameter
34 | * management. It stores all the query parameters in a LinkedList, so parameters can
35 | * be looked up, removed, and added easily.
36 | */
37 | class UriBuilder {
38 |
39 | public static class QueryParameter {
40 | private final String key;
41 | private final String value;
42 |
43 | /**
44 | * Constructs a query parameter with no value (e.g., download).
45 | *
46 | * @param key
47 | */
48 | public QueryParameter(String key) {
49 | if (key == null) throw new AssertionError();
50 |
51 | this.key = key;
52 | this.value = null;
53 | }
54 |
55 | public QueryParameter(String key, String value) {
56 | if (key == null) throw new AssertionError();
57 | if (value == null) throw new AssertionError();
58 |
59 | this.key = key;
60 | this.value = value;
61 | }
62 |
63 | public String getKey() {
64 | return this.key;
65 | }
66 |
67 | public String getValue() {
68 | return this.value;
69 | }
70 |
71 | public boolean hasValue() {
72 | return this.value != null;
73 | }
74 |
75 | @Override
76 | public String toString() {
77 | if (this.hasValue()) {
78 | return this.key + "=" + this.value;
79 | }
80 |
81 | return this.key;
82 | }
83 | }
84 |
85 | private static final String EQUAL = "=";
86 | private static final String AMPERSAND = "&";
87 | private static final char FORWARD_SLASH = '/';
88 |
89 | private String scheme;
90 | private String host;
91 | private StringBuilder path;
92 |
93 | private final LinkedList queryParameters;
94 |
95 | /**
96 | * Constructs a new UriBuilder from the given Uri.
97 | *
98 | * @return a new Uri Builder based off the given Uri.
99 | */
100 | public static UriBuilder newInstance(Uri uri) {
101 | return new UriBuilder().scheme(uri.getScheme())
102 | .host(uri.getHost())
103 | .path(uri.getPath())
104 | .query(uri.getQuery());
105 | }
106 |
107 | public UriBuilder() {
108 | this.queryParameters = new LinkedList();
109 | }
110 |
111 | /**
112 | * Appends a new query parameter to the UriBuilder's query string.
113 | *
114 | * (e.g., appendQueryParameter("k1", "v1") when UriBuilder's query string is
115 | * k2=v2&k3=v3 results in k2=v2&k3=v3&k1=v1).
116 | *
117 | * @param key Key of the new query parameter.
118 | * @param value Value of the new query parameter.
119 | * @return this UriBuilder object. Useful for chaining.
120 | */
121 | public UriBuilder appendQueryParameter(String key, String value) {
122 | if (key == null) throw new AssertionError();
123 | if (value == null) throw new AssertionError();
124 |
125 | this.queryParameters.add(new QueryParameter(key, value));
126 |
127 | return this;
128 | }
129 |
130 | /**
131 | * Appends the given query string on to the existing UriBuilder's query parameters.
132 | *
133 | * (e.g., UriBuilder's queryString k1=v1&k2=v2 and given queryString k3=v3&k4=v4, results in
134 | * k1=v1&k2=v2&k3=v3&k4=v4).
135 | *
136 | * @param queryString Key-Value pairs separated by & and = (e.g., k1=v1&k2=v2&k3=k3).
137 | * @return this UriBuilder object. Useful for chaining.
138 | */
139 | public UriBuilder appendQueryString(String queryString) {
140 | if (queryString == null) {
141 | return this;
142 | }
143 |
144 | String[] pairs = TextUtils.split(queryString, UriBuilder.AMPERSAND);
145 | for(String pair : pairs) {
146 | String[] splitPair = TextUtils.split(pair, UriBuilder.EQUAL);
147 | if (splitPair.length == 2) {
148 | String key = splitPair[0];
149 | String value = splitPair[1];
150 |
151 | this.queryParameters.add(new QueryParameter(key, value));
152 | } else if (splitPair.length == 1){
153 | String key = splitPair[0];
154 |
155 | this.queryParameters.add(new QueryParameter(key));
156 | } else {
157 | Log.w("live.auth.UriBuilder", "Invalid query parameter: " + pair);
158 | }
159 | }
160 |
161 | return this;
162 | }
163 |
164 | /**
165 | * Appends the given path to the UriBuilder's current path.
166 | *
167 | * @param path The path to append onto this UriBuilder's path.
168 | * @return this UriBuilder object. Useful for chaining.
169 | */
170 | public UriBuilder appendToPath(String path) {
171 | if (path == null) throw new AssertionError();
172 |
173 | if (this.path == null) {
174 | this.path = new StringBuilder(path);
175 | } else {
176 | boolean endsWithSlash = TextUtils.isEmpty(this.path) ? false :
177 | this.path.charAt(this.path.length() - 1) == UriBuilder.FORWARD_SLASH;
178 | boolean pathIsEmpty = TextUtils.isEmpty(path);
179 | boolean beginsWithSlash =
180 | pathIsEmpty ? false : path.charAt(0) == UriBuilder.FORWARD_SLASH;
181 |
182 | if (endsWithSlash && beginsWithSlash) {
183 | if (path.length() > 1) {
184 | this.path.append(path.substring(1));
185 |
186 | }
187 | } else if (!endsWithSlash && !beginsWithSlash) {
188 | if (!pathIsEmpty) {
189 | this.path.append(UriBuilder.FORWARD_SLASH).append(path);
190 | }
191 | } else {
192 | this.path.append(path);
193 | }
194 | }
195 |
196 | return this;
197 | }
198 |
199 | /**
200 | * Builds the Uri by converting into a android.net.Uri object.
201 | *
202 | * @return a new android.net.Uri defined by what was given to the builder.
203 | */
204 | public Uri build() {
205 | return new Uri.Builder().scheme(this.scheme)
206 | .authority(this.host)
207 | .path(this.path == null ? "" : this.path.toString())
208 | .encodedQuery(TextUtils.join("&", this.queryParameters))
209 | .build();
210 | }
211 |
212 | /**
213 | * Sets the host part of the Uri.
214 | *
215 | * @return this UriBuilder object. Useful for chaining.
216 | */
217 | public UriBuilder host(String host) {
218 | if (host == null) throw new AssertionError();
219 | this.host = host;
220 |
221 | return this;
222 | }
223 |
224 | /**
225 | * Sets the path and removes any previously existing path.
226 | *
227 | * @param path The path to set on this UriBuilder.
228 | * @return this UriBuilder object. Useful for chaining.
229 | */
230 | public UriBuilder path(String path) {
231 | if (path == null) throw new AssertionError();
232 | this.path = new StringBuilder(path);
233 |
234 | return this;
235 | }
236 |
237 | /**
238 | * Takes a query string and puts it in the Uri Builder's query string removing
239 | * any existing query parameters.
240 | *
241 | * @param queryString Key-Value pairs separated by & and = (e.g., k1=v1&k2=v2&k3=k3).
242 | * @return this UriBuilder object. Useful for chaining.
243 | */
244 | public UriBuilder query(String queryString) {
245 | this.queryParameters.clear();
246 |
247 | return this.appendQueryString(queryString);
248 | }
249 |
250 | /**
251 | * Removes all query parameters from the UriBuilder that has the given key.
252 | *
253 | * (e.g., removeQueryParametersWithKey("k1") when UriBuilder's query string of k1=v1&k2=v2&k1=v3
254 | * results in k2=v2).
255 | *
256 | * @param key Query parameter's key to remove
257 | * @return this UriBuilder object. Useful for chaining.
258 | */
259 | public UriBuilder removeQueryParametersWithKey(String key) {
260 | // There could be multiple query parameters with this key and
261 | // we want to remove all of them.
262 | Iterator it = this.queryParameters.iterator();
263 |
264 | while (it.hasNext()) {
265 | QueryParameter qp = it.next();
266 | if (qp.getKey().equals(key)) {
267 | it.remove();
268 | }
269 | }
270 |
271 | return this;
272 | }
273 |
274 | /**
275 | * Sets the scheme part of the Uri.
276 | *
277 | * @return this UriBuilder object. Useful for chaining.
278 | */
279 | public UriBuilder scheme(String scheme) {
280 | if (scheme == null) throw new AssertionError();
281 | this.scheme = scheme;
282 |
283 | return this;
284 | }
285 |
286 | /**
287 | * Returns the URI in string format (e.g., http://foo.com/bar?k1=v2).
288 | */
289 | @Override
290 | public String toString() {
291 | return this.build().toString();
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | msa-auth
3 |
4 |
--------------------------------------------------------------------------------