diff --git a/README.md b/README.md index 421c8bd..15638aa 100644 --- a/README.md +++ b/README.md @@ -21,29 +21,29 @@ ## Installing the SDK: 1. Add the JitPack repository to the your root `build.gradle` file at the end of the repository. - ```gradle - allprojects { - repositories { - ... - maven { url 'https://jitpack.io' } - } - } - ``` +```gradle +allprojects { + repositories { + ... + maven { url 'https://jitpack.io' } + } +} +``` 2. Add the dependency for the App ID client SDK: - ```gradle - dependencies { - compile 'com.github.ibm-cloud-security:appid-clientsdk-android:2.+' - } - ``` +```gradle +dependencies { + compile 'com.github.ibm-cloud-security:appid-clientsdk-android:3.+' +} +``` 3. In your Android project in Android Studio, open the build.gradle file of your app module (not the project build.gradle), and add the following line to the defaultConfig: - ``` - defaultConfig { +``` +defaultConfig { ... manifestPlaceholders = ['appIdRedirectScheme': android.defaultConfig.applicationId] - } - ``` +} +``` ## Initializing the App ID client SDK @@ -60,165 +60,162 @@ Use the LoginWidget class to start the authorization flow. ```java LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); loginWidget.launch(this, new AuthorizationListener() { - @Override - public void onAuthorizationFailure (AuthorizationException exception) { - //Exception occurred - } - - @Override - public void onAuthorizationCanceled () { - //Authentication canceled by the user - } - - @Override - public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken) { - //User authenticated - } - }); + @Override + public void onAuthorizationFailure (AuthorizationException exception) { + //Exception occurred + } + + @Override + public void onAuthorizationCanceled () { + //Authentication canceled by the user + } + + @Override + public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + //User authenticated + } +}); ``` **Note**: * The default configuration use Facebook and Google as authentication options. If you configure only one of them the login widget will *not* launch and the user will be redirected to the configured identity provider authentication screen. * When using Cloud Directory, and "Email verification" is configured to *not* allow users to sign-in without email verification, then the "onAuthorizationSuccess" of the "AuthorizationListener" will be invoked without tokens. -## Managing Cloud Directory with the Android SDK -{: #managing-android} +## Managing Cloud Directory with the Android SDK Make sure to set Cloud Directory identity provider to ON in AppID dashboard, when using the following APIs. - ### Login using Resource Owner Password - You can obtain access token and id token by supplying the end user's username and the end user's password. - ```java - AppID.getInstance().obtainTokensWithROP(getApplicationContext(), username, password, - new TokenResponseListener() { - @Override - public void onAuthorizationFailure (AuthorizationException exception) { - //Exception occurred - } - - @Override - public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken) { - //User authenticated - } - }); - ``` - ### Sign Up +### Login using Resource Owner Password + You can obtain access token, id token and refresh token by supplying the end user's username and password. +```java +AppID.getInstance().signinWithResourceOwnerPassword(getApplicationContext(), username, password, new TokenResponseListener() { + @Override + public void onAuthorizationFailure (AuthorizationException exception) { + //Exception occurred + } + + @Override + public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + //User authenticated + } +}); +``` + +### Sign Up Make sure to set **Allow users to sign up and reset their password** to **ON**, in the settings for Cloud Directory. Use the LoginWidget class to start the sign up flow. - ```java - LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); - loginWidget.launchSignUp(this, new AuthorizationListener() { - @Override - public void onAuthorizationFailure (AuthorizationException exception) { - //Exception occurred - } - - @Override - public void onAuthorizationCanceled () { - //Sign up canceled by the user - } - - @Override - public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken) { - if (accessToken != null && identityToken != null) { - //User authenticated - } else { - //email verification is required - } - - } - }); - ``` - ### Forgot Password +```java +LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); +loginWidget.launchSignUp(this, new AuthorizationListener() { + @Override + public void onAuthorizationFailure (AuthorizationException exception) { + //Exception occurred + } + + @Override + public void onAuthorizationCanceled () { + //Sign up canceled by the user + } + + @Override + public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + if (accessToken != null && identityToken != null) { + //User authenticated + } else { + //email verification is required + } + } +}); +``` +### Forgot Password Make sure to set **Allow users to sign up and reset their password** and **Forgot password email** to **ON**, in the settings for Cloud Directory Use LoginWidget class to start the forgot password flow. - ```java - LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); - loginWidget.launchForgotPassword(this, new AuthorizationListener() { - @Override - public void onAuthorizationFailure (AuthorizationException exception) { - //Exception occurred - } - - @Override - public void onAuthorizationCanceled () { - //forogt password canceled by the user - } - - @Override - public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken) { - //forgot password finished, in this case accessToken and identityToken will be null. - - } - }); - ``` - ### Change Details +```java +LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); +loginWidget.launchForgotPassword(this, new AuthorizationListener() { + @Override + public void onAuthorizationFailure (AuthorizationException exception) { + //Exception occurred + } + + @Override + public void onAuthorizationCanceled () { + // Forogt password canceled by the user + } + + @Override + public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + // Forgot password finished, in this case accessToken and identityToken will be null. + } +}); +``` +### Change Details Make sure to set **Allow users to sign up and reset their password** to **ON**, in Cloud Directory settings that are in AppID dashboard. Use LoginWidget class to start the change details flow. This API can be used only when the user is logged in using Cloud Directory identity provider. - ```java - LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); - loginWidget.launchChangeDetails(this, new AuthorizationListener() { - @Override - public void onAuthorizationFailure (AuthorizationException exception) { - //Exception occurred - } - - @Override - public void onAuthorizationCanceled () { - //changed details canceled by the user - } - - @Override - public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken) { - //User authenticated, and fresh tokens received - } - }); - ``` - ### Change Password +```java +LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); +loginWidget.launchChangeDetails(this, new AuthorizationListener() { + @Override + public void onAuthorizationFailure (AuthorizationException exception) { + // Exception occurred + } + + @Override + public void onAuthorizationCanceled () { + // Changed details canceled by the user + } + + @Override + public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + // User authenticated, and fresh tokens received + } +}); +``` +### Change Password Make sure to set **Allow users to sign up and reset their password** to **ON**, in the settings for Cloud Directory. Use LoginWidget class to start the change password flow. This API can be used only when the user logged in by using Cloud Directory as their identity provider. - ```java - LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); - loginWidget.launchChangePassword(this, new AuthorizationListener() { - @Override - public void onAuthorizationFailure (AuthorizationException exception) { - //Exception occurred - } +```java +LoginWidget loginWidget = AppID.getInstance().getLoginWidget(); +loginWidget.launchChangePassword(this, new AuthorizationListener() { + @Override + public void onAuthorizationFailure (AuthorizationException exception) { + // Exception occurred + } - @Override - public void onAuthorizationCanceled () { - //change password canceled by the user - } + @Override + public void onAuthorizationCanceled () { + // Change password canceled by the user + } - @Override - public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken) { - //User authenticated, and fresh tokens received - } - }); - ``` + @Override + public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + // User authenticated, and fresh tokens received + } +}); +``` ## Anonymous Login ```java -AppID.getInstance().loginAnonymously(getApplicationContext(), new AuthorizationListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - //Exception occurred - } - - @Override - public void onAuthorizationCanceled() { - //Authentication canceled by the user - } - - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - //User authenticated - } - }); +AppID.getInstance().signinAnonymously(getApplicationContext(), new AuthorizationListener() { + @Override + public void onAuthorizationFailure(AuthorizationException exception) { + //Exception occurred + } + + @Override + public void onAuthorizationCanceled() { + //Authentication canceled by the user + } + + @Override + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + //User authenticated + } +}); ``` ## User profile attributes @@ -227,52 +224,52 @@ AppID appId = AppID.getInstance(); String name = "name"; String value = "value"; appId.getUserAttributeManager().setAttribute(name, value, new UserAttributeResponseListener() { - @Override - public void onSuccess(JSONObject attributes) { - //Set attribute "name" to "value" successfully - } - - @Override - public void onFailure(UserAttributesException e) { - //Exception occurred - } - }); + @Override + public void onSuccess(JSONObject attributes) { + // Set attribute "name" to "value" successfully + } + + @Override + public void onFailure(UserAttributesException e) { + // Exception occurred + } +}); appId.getUserAttributeManager().getAttribute(name, new UserAttributeResponseListener() { - @Override - public void onSuccess(JSONObject attributes) { - //Got attribute "name" successfully - } - - @Override - public void onFailure(UserAttributesException e) { - //Exception occurred - } - }); + @Override + public void onSuccess(JSONObject attributes) { + // Got attribute "name" successfully + } + + @Override + public void onFailure(UserAttributesException e) { + // Exception occurred + } +}); -appId.getUserAttributeManager().getAllAttributes( new UserAttributeResponseListener() { - @Override - public void onSuccess(JSONObject attributes) { - //Got all attributes successfully - } - - @Override - public void onFailure(UserAttributesException e) { - //Exception occurred - } - }); +appId.getUserAttributeManager().getAllAttributes(new UserAttributeResponseListener() { + @Override + public void onSuccess(JSONObject attributes) { + // Got all attributes successfully + } + + @Override + public void onFailure(UserAttributesException e) { + // Exception occurred + } +}); appId.getUserAttributeManager().deleteAttribute(name, new UserAttributeResponseListener() { - @Override - public void onSuccess(JSONObject attributes) { - //Attribute "name" deleted successfully - } - - @Override - public void onFailure(UserAttributesException e) { - //Exception occurred - } - }); + @Override + public void onSuccess(JSONObject attributes) { + // Attribute "name" deleted successfully + } + + @Override + public void onFailure(UserAttributesException e) { + // Exception occurred + } +}); ``` ## Invoking protected resources @@ -285,10 +282,12 @@ bmsClient.setAuthorizationManager(appIdAuthMgr); Request request = new Request("http://my-mobile-backend.mybluemix.net/protected", Request.GET); request.send(this, new ResponseListener() { + @Override public void onSuccess (Response response) { Log.d("Myapp", "onRegistrationSuccess :: " + response.getResponseText()); } + @Override public void onFailure (Response response, Throwable t, JSONObject extendedInfo) { if (null != t) { diff --git a/app/src/main/java/com/ibm/mobilefirstplatform/appid/MainActivity.java b/app/src/main/java/com/ibm/mobilefirstplatform/appid/MainActivity.java index cbca033..1592a7c 100644 --- a/app/src/main/java/com/ibm/mobilefirstplatform/appid/MainActivity.java +++ b/app/src/main/java/com/ibm/mobilefirstplatform/appid/MainActivity.java @@ -20,6 +20,7 @@ import com.ibm.bluemix.appid.android.api.TokenResponseListener; import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.api.userattributes.UserAttributeResponseListener; import com.ibm.bluemix.appid.android.api.userattributes.UserAttributesException; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.BMSClient; @@ -44,6 +45,7 @@ public class MainActivity extends AppCompatActivity { private AccessToken anonymousAccessToken; private AccessToken identifiedAccessToken; private AccessToken useThisToken; + private RefreshToken identifiedRefreshToken; public final static int LOGIN_SUBMITTED = 2; public final static int LOGIN_CANCEL = 3; @@ -67,7 +69,7 @@ protected void onCreate(Bundle savedInstanceState) { public void onAnonLoginClick(View v) { logger.debug("onAnonLoginClick"); showProgress(); - appId.loginAnonymously(getApplicationContext(), new AuthorizationListener() { + appId.signinAnonymously(getApplicationContext(), new AuthorizationListener() { @Override public void onAuthorizationFailure(AuthorizationException exception) { logger.error("Anonymous authorization failure"); @@ -84,7 +86,7 @@ public void onAuthorizationCanceled() { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { logger.info("Anonymous authorization success"); anonymousAccessToken = accessToken; extractAndDisplayDataFromIdentityToken(identityToken); @@ -115,14 +117,18 @@ public void onAuthorizationCanceled() { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { logger.info("onAuthorizationSuccess"); if (accessToken != null && identityToken != null) { logger.info("access_token: " + accessToken.getRaw()); logger.info("id_token: " + identityToken.getRaw()); logger.info("access_token isExpired: " + accessToken.isExpired()); logger.info("id_token isExpired: " + identityToken.isExpired()); + if (refreshToken != null) { + logger.info("refresh_token: " + refreshToken.getRaw()); + } identifiedAccessToken = accessToken; + identifiedRefreshToken = refreshToken; extractAndDisplayDataFromIdentityToken(identityToken); } else { //in case we are in strict mode @@ -152,14 +158,18 @@ public void onAuthorizationCanceled() { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { logger.info("sign up: onAuthorizationSuccess"); if (accessToken != null && identityToken != null) { logger.info("access_token: " + accessToken.getRaw()); logger.info("id_token: " + identityToken.getRaw()); logger.info("access_token isExpired: " + accessToken.isExpired()); logger.info("id_token isExpired: " + identityToken.isExpired()); + if (refreshToken != null) { + logger.info("refresh_token: " + refreshToken.getRaw()); + } identifiedAccessToken = accessToken; + identifiedRefreshToken = refreshToken; extractAndDisplayDataFromIdentityToken(identityToken); } else { //in case we are in strict mode @@ -190,7 +200,7 @@ public void onAuthorizationCanceled() { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { logger.info("Forgot Password: onAuthorizationSuccess"); hideProgress(); } @@ -217,13 +227,17 @@ public void onAuthorizationCanceled() { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { logger.info("Change Password: onAuthorizationSuccess"); logger.info("access_token: " + accessToken.getRaw()); logger.info("id_token: " + identityToken.getRaw()); logger.info("access_token isExpired: " + accessToken.isExpired()); logger.info("id_token isExpired: " + identityToken.isExpired()); + if (refreshToken != null) { + logger.info("refresh_token: " + refreshToken.getRaw()); + } identifiedAccessToken = accessToken; + identifiedRefreshToken = refreshToken; extractAndDisplayDataFromIdentityToken(identityToken); } }); @@ -249,13 +263,17 @@ public void onAuthorizationCanceled() { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { logger.info("Change Details: onAuthorizationSuccess"); logger.info("access_token: " + accessToken.getRaw()); logger.info("id_token: " + identityToken.getRaw()); logger.info("access_token isExpired: " + accessToken.isExpired()); logger.info("id_token isExpired: " + identityToken.isExpired()); + if (refreshToken != null) { + logger.info("refresh_token: " + refreshToken.getRaw()); + } identifiedAccessToken = accessToken; + identifiedRefreshToken = refreshToken; extractAndDisplayDataFromIdentityToken(identityToken); } }); @@ -280,7 +298,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == LOGIN_SUBMITTED && data != null) { String username = data.getStringExtra("username"); String password = data.getStringExtra("password"); - appId.obtainTokensWithROP(getApplicationContext(), username, password, new TokenResponseListener() { + appId.signinWithResourceOwnerPassword(getApplicationContext(), username, password, new TokenResponseListener() { @Override public void onAuthorizationFailure(AuthorizationException exception) { logger.info("onAuthorizationFailure: " + exception.getMessage()); @@ -289,13 +307,17 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { logger.info("onAuthorizationSuccess"); logger.info("access_token: " + accessToken.getRaw()); logger.info("id_token: " + identityToken.getRaw()); logger.info("access_token isExpired: " + accessToken.isExpired()); logger.info("id_token isExpired: " + identityToken.isExpired()); + if (refreshToken != null) { + logger.info("refresh_token: " + refreshToken.getRaw()); + } identifiedAccessToken = accessToken; + identifiedRefreshToken = refreshToken; extractAndDisplayDataFromIdentityToken(identityToken); } }, anonymousAccessToken != null ? anonymousAccessToken.getRaw() : null); @@ -552,6 +574,9 @@ public void onRadioButtonClicked(View view) { useThisToken.getRaw() : "No token"; ((TextView) findViewById(R.id.textViewProtectedResourceResponse)).setText(token); break; + case R.id.radio_refresh: + ((TextView) findViewById(R.id.textViewProtectedResourceResponse)).setText(identifiedRefreshToken != null ? identifiedRefreshToken.getRaw() : "No token"); + break; } } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0cb6456..893468c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -315,6 +315,13 @@ android:onClick="onRadioButtonClicked" android:text="@string/use_identified" /> + + Help last token - anonymous token + anon token id token Tokens Close NO\nTOKENS + refresh token diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/api/AppID.java b/lib/src/main/java/com/ibm/bluemix/appid/android/api/AppID.java index 3ec3bef..a478070 100644 --- a/lib/src/main/java/com/ibm/bluemix/appid/android/api/AppID.java +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/api/AppID.java @@ -17,6 +17,7 @@ import android.support.annotation.NonNull; import com.ibm.bluemix.appid.android.api.tokens.AccessToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.api.userattributes.UserAttributeManager; import com.ibm.bluemix.appid.android.internal.OAuthManager; import com.ibm.bluemix.appid.android.internal.loginwidget.LoginWidgetImpl; @@ -24,6 +25,8 @@ import org.jetbrains.annotations.NotNull; +import java.util.Locale; + public class AppID { private static AppID instance; @@ -109,6 +112,17 @@ public LoginWidget getLoginWidget() { return this.loginWidget; } + /** + * Sets the preferred locale for UI pages + * @param locale + */ + public void setPreferredLocale(Locale locale) { + if (null == oAuthManager) { + throw new RuntimeException("AppID is not initialized. Use .initialize() first."); + } + oAuthManager.setPreferredLocale(locale); + } + /** * @return the OAuth Manager */ @@ -131,16 +145,40 @@ public UserAttributeManager getUserAttributeManager(){ return this.userAttributeManager; } + /** + * @deprecated use {@link #signinAnonymously(Context, AuthorizationListener)} + */ + @Deprecated public void loginAnonymously(@NotNull Context context, @NotNull AuthorizationListener authorizationListener){ - this.loginAnonymously(context, null, true, authorizationListener); + this.signinAnonymously(context, authorizationListener); } + /** + * @deprecated use {@link #signinAnonymously(Context, String, AuthorizationListener)} + */ + @Deprecated public void loginAnonymously(@NotNull Context context, String accessToken, @NotNull AuthorizationListener authorizationListener){ - this.loginAnonymously(context, accessToken, true, authorizationListener); + this.signinAnonymously(context, accessToken, authorizationListener); + } + + /** + * @deprecated use {@link #signinAnonymously(Context, String, boolean, AuthorizationListener)} + */ + @Deprecated + public void loginAnonymously(@NotNull Context context, String accessToken, boolean allowCreateNewAnonymousUser, @NotNull AuthorizationListener authorizationListener){ + oAuthManager.getAuthorizationManager().signinAnonymously(context, accessToken, allowCreateNewAnonymousUser, authorizationListener); + } + + public void signinAnonymously(@NotNull Context context, @NotNull AuthorizationListener authorizationListener){ + this.signinAnonymously(context, null, true, authorizationListener); } - public void loginAnonymously(@NotNull Context context, String accessToken, boolean allowCreateNewAnonymousUser, @NotNull AuthorizationListener authorizationListener){ - oAuthManager.getAuthorizationManager().loginAnonymously(context, accessToken, allowCreateNewAnonymousUser, authorizationListener); + public void signinAnonymously(@NotNull Context context, String accessToken, @NotNull AuthorizationListener authorizationListener){ + this.signinAnonymously(context, accessToken, true, authorizationListener); + } + + public void signinAnonymously(@NotNull Context context, String accessToken, boolean allowCreateNewAnonymousUser, @NotNull AuthorizationListener authorizationListener){ + oAuthManager.getAuthorizationManager().signinAnonymously(context, accessToken, allowCreateNewAnonymousUser, authorizationListener); } /** @@ -150,28 +188,69 @@ public void loginAnonymously(@NotNull Context context, String accessToken, bool * @param password the resource owner password * @param tokenResponseListener the token response listener */ - public void obtainTokensWithROP(@NotNull Context context, @NotNull String username, @NotNull String password, @NotNull TokenResponseListener tokenResponseListener) { + public void signinWithResourceOwnerPassword(@NotNull Context context, @NotNull String username, @NotNull String password, @NotNull TokenResponseListener tokenResponseListener) { AccessToken accessToken = oAuthManager.getTokenManager().getLatestAccessToken(); if (accessToken != null && accessToken.isAnonymous()) { - oAuthManager.getAuthorizationManager().obtainTokensWithROP(context, username, password, accessToken.getRaw(), tokenResponseListener); + oAuthManager.getAuthorizationManager().signinWithResourceOwnerPassword(context, username, password, accessToken.getRaw(), tokenResponseListener); } - oAuthManager.getAuthorizationManager().obtainTokensWithROP(context, username, password, null, tokenResponseListener); + oAuthManager.getAuthorizationManager().signinWithResourceOwnerPassword(context, username, password, null, tokenResponseListener); } - /** - * Obtain token using Resource owner Password (RoP). - * - * @param username the resource owner username - * @param password the resource owner password - * @param tokenResponseListener the token response listener - * @param accessTokenString previous access token of some anonymous user - */ + /** + * @deprecated use {@link #obtainTokensWithROP(Context, String, String, TokenResponseListener, String)} + */ public void obtainTokensWithROP(@NotNull Context context, @NotNull String username, @NotNull String password, @NotNull TokenResponseListener tokenResponseListener, String accessTokenString) { - if(accessTokenString == null) { - obtainTokensWithROP(context, username, password, tokenResponseListener); + signinWithResourceOwnerPassword(context, username, password, tokenResponseListener, accessTokenString); + } + + /** + * @deprecated use {@link #obtainTokensWithROP(Context, String, String, TokenResponseListener)} + */ + public void obtainTokensWithROP(@NotNull Context context, @NotNull String username, @NotNull String password, @NotNull TokenResponseListener tokenResponseListener) { + signinWithResourceOwnerPassword(context, username, password, tokenResponseListener); + } + + /** + * Obtain token using Resource owner Password (RoP). + * + * @param username the resource owner username + * @param password the resource owner password + * @param tokenResponseListener the token response listener + * @param accessTokenString previous access token of some anonymous user + */ + public void signinWithResourceOwnerPassword(@NotNull Context context, @NotNull String username, @NotNull String password, @NotNull TokenResponseListener tokenResponseListener, String accessTokenString) { + if(accessTokenString == null) { + signinWithResourceOwnerPassword(context, username, password, tokenResponseListener); } else { - oAuthManager.getAuthorizationManager().obtainTokensWithROP(context, username, password, accessTokenString, tokenResponseListener); + oAuthManager.getAuthorizationManager().signinWithResourceOwnerPassword(context, username, password, accessTokenString, tokenResponseListener); } } + /** + * Obtain token using a refresh token + * + * @param refreshToken the refresh token + * @param tokenResponseListener the token response listener + */ + public void signinWithRefreshToken(@NotNull Context context, @NotNull String refreshToken, @NotNull TokenResponseListener tokenResponseListener) { + if (refreshToken == null) { + tokenResponseListener.onAuthorizationFailure(new AuthorizationException("Missing refresh-token")); + return; + } + oAuthManager.getAuthorizationManager().signinWithRefreshToken(context, refreshToken, tokenResponseListener); + } + + /** + * Obtain token using the latest refresh token stored in the SDK + * + * @param tokenResponseListener the token response listener + */ + public void signinWithRefreshToken(@NotNull Context context, @NotNull TokenResponseListener tokenResponseListener) { + String refreshTokenString = null; + RefreshToken refreshToken = oAuthManager.getTokenManager().getLatestRefreshToken(); + if (refreshToken != null) { + refreshTokenString = refreshToken.getRaw(); + } + signinWithRefreshToken(context, refreshTokenString, tokenResponseListener); + } } diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/api/AppIDAuthorizationManager.java b/lib/src/main/java/com/ibm/bluemix/appid/android/api/AppIDAuthorizationManager.java index 7479494..5cf9fce 100644 --- a/lib/src/main/java/com/ibm/bluemix/appid/android/api/AppIDAuthorizationManager.java +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/api/AppIDAuthorizationManager.java @@ -20,8 +20,9 @@ import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; -import com.ibm.bluemix.appid.android.internal.helpers.AuthorizationHeaderHelper; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.internal.OAuthManager; +import com.ibm.bluemix.appid.android.internal.helpers.AuthorizationHeaderHelper; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.ResponseListener; import com.ibm.mobilefirstplatform.clientsdk.android.logger.api.Logger; import com.ibm.mobilefirstplatform.clientsdk.android.security.api.AppIdentity; @@ -70,7 +71,7 @@ public boolean isAuthorizationRequired (int statusCode, Map /** * A response is an OAuth error response only if, - * 1. it's status is 401 or 403 + * 1. Its status is either 401 or 403 * 2. The value of the "WWW-Authenticate" header contains 'Bearer' * * @param urlConnection connection to check the authorization conditions for. @@ -87,6 +88,25 @@ public boolean isAuthorizationRequired (HttpURLConnection urlConnection) throws public void obtainAuthorization (final Context context, final ResponseListener listener, Object... params) { logger.debug("obtainAuthorization"); + RefreshToken latestRefreshToken = getRefreshToken(); + if (latestRefreshToken != null) { + oAuthManager.getTokenManager().obtainTokensRefreshToken(latestRefreshToken.getRaw(), new TokenResponseListener() { + @Override + public void onAuthorizationFailure(AuthorizationException exception) { + launchAuthorization(context, listener); + } + + @Override + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + listener.onSuccess(null); + } + }); + } else { + launchAuthorization(context, listener); + } + } + + private void launchAuthorization (final Context context, final ResponseListener listener) { oAuthManager.getAuthorizationManager().launchAuthorizationUI((Activity)context, new AuthorizationListener() { @Override public void onAuthorizationFailure (AuthorizationException exception) { @@ -99,7 +119,7 @@ public void onAuthorizationCanceled () { } @Override - public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { listener.onSuccess(null); } }); @@ -185,4 +205,8 @@ public AccessToken getAccessToken () { public IdentityToken getIdentityToken () { return oAuthManager.getTokenManager().getLatestIdentityToken(); } + + public RefreshToken getRefreshToken() { + return oAuthManager.getTokenManager().getLatestRefreshToken(); + } } diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/api/TokenResponseListener.java b/lib/src/main/java/com/ibm/bluemix/appid/android/api/TokenResponseListener.java index c435a54..1f265e2 100644 --- a/lib/src/main/java/com/ibm/bluemix/appid/android/api/TokenResponseListener.java +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/api/TokenResponseListener.java @@ -15,8 +15,9 @@ import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; public interface TokenResponseListener { void onAuthorizationFailure(AuthorizationException exception); - void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken); + void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken); } diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/api/tokens/RefreshToken.java b/lib/src/main/java/com/ibm/bluemix/appid/android/api/tokens/RefreshToken.java new file mode 100644 index 0000000..d385646 --- /dev/null +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/api/tokens/RefreshToken.java @@ -0,0 +1,18 @@ +/* + Copyright 2017 IBM Corp. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.ibm.bluemix.appid.android.api.tokens; + +public interface RefreshToken { + String getRaw(); +} diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/OAuthManager.java b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/OAuthManager.java index 729a141..3398dcb 100644 --- a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/OAuthManager.java +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/OAuthManager.java @@ -21,6 +21,8 @@ import com.ibm.bluemix.appid.android.internal.registrationmanager.RegistrationManager; import com.ibm.bluemix.appid.android.internal.tokenmanager.TokenManager; +import java.util.Locale; + public class OAuthManager { private AppID appId; @@ -57,4 +59,8 @@ public TokenManager getTokenManager () { return tokenManager; } + public void setPreferredLocale(Locale locale) { + authorizationManager.setPreferredLocale(locale); + } + } diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationManager.java b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationManager.java index 426db1c..feedff2 100644 --- a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationManager.java +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationManager.java @@ -39,6 +39,8 @@ import org.json.JSONException; import org.json.JSONObject; +import java.util.Locale; + public class AuthorizationManager { private final static String OAUTH_AUTHORIZATION_PATH = "/authorization"; private final static String CHANGE_PASSWORD_PATH = "/cloud_directory/change_password"; @@ -57,6 +59,7 @@ public class AuthorizationManager { private final static String RESPONSE_TYPE_CODE = "code"; private final static String RESPONSE_TYPE_SIGN_UP = "sign_up"; private final static String USER_ID = "user_id"; + private final static String LOCALE_PARAM_NAME = "language"; private final static String SCOPE = "scope"; private final static String SCOPE_OPENID = "openid"; @@ -70,6 +73,7 @@ public class AuthorizationManager { private final static String CODE = "code"; private String serverUrl; + private Locale locale; private final static Logger logger = Logger.getLogger(Logger.INTERNAL_PREFIX + AuthorizationManager.class.getName()); @@ -105,6 +109,9 @@ private String getAuthorizationUrl(String idpName, AccessToken accessToken, Stri if (accessToken != null) { builder.appendQueryParameter(APPID_ACCESS_TOKEN, accessToken.getRaw()); } + + addLocaleToUri(builder); + return builder.build().toString(); } @@ -144,9 +151,17 @@ private String buildUrl(String endpointUrl, String userId, String redirectUri, S if (null != userId) { builder.appendQueryParameter(USER_ID, userId); } + + addLocaleToUri(builder); + return builder.build().toString(); } + private void addLocaleToUri(Uri.Builder builder) { + Locale localeToUse = locale != null ? locale : Locale.getDefault(); + builder.appendQueryParameter(LOCALE_PARAM_NAME, localeToUse.toString()); + } + public void launchAuthorizationUI(final Activity activity, final AuthorizationListener authorizationListener) { launchAuthorizationUI(activity, null, authorizationListener); } @@ -313,17 +328,17 @@ private void continueAnonymousLogin(String accessTokenString, boolean allowCreat request.send(new ResponseListener() { @Override public void onSuccess(Response response) { - logger.debug("loginAnonymously.Response in onSuccess:" + response.getResponseText()); + logger.debug("signinAnonymously.Response in onSuccess:" + response.getResponseText()); String location = response.getHeaders().get("Location").toString(); String locationUrl = location.substring(1, location.length() - 1); // removing [] String code = Uri.parse(locationUrl).getQueryParameter("code"); - oAuthManager.getTokenManager().obtainTokens(code, listener); + oAuthManager.getTokenManager().obtainTokensAuthCode(code, listener); } @Override public void onFailure(Response response, Throwable t, JSONObject extendedInfo) { String message = (response == null) ? "" : response.getResponseText(); - logger.debug("loginAnonymously.Response in onFailure:" + message, t); + logger.debug("signinAnonymously.Response in onFailure:" + message, t); message = (t != null) ? t.getLocalizedMessage() : "Authorization request failed."; message = (extendedInfo != null) ? message + extendedInfo.toString() : message; listener.onAuthorizationFailure(new AuthorizationException(message)); @@ -332,7 +347,7 @@ public void onFailure(Response response, Throwable t, JSONObject extendedInfo) { ); } - public void loginAnonymously(final Context context, final String accessTokenString, final boolean allowCreateNewAnonymousUser, final AuthorizationListener authorizationListener) { + public void signinAnonymously(final Context context, final String accessTokenString, final boolean allowCreateNewAnonymousUser, final AuthorizationListener authorizationListener) { registrationManager.ensureRegistered(context, new RegistrationListener() { @Override public void onRegistrationFailure(RegistrationStatus error) { @@ -351,7 +366,11 @@ public void setAppIDRequestFactory(AppIDRequestFactory appIDRequestFactory) { this.appIDRequestFactory = appIDRequestFactory; } - public void obtainTokensWithROP(final Context context, final String username, final String password, final String accessTokenString, final TokenResponseListener tokenResponseListener) { + public void setPreferredLocale(Locale locale) { + this.locale = locale; + } + + public void signinWithResourceOwnerPassword(final Context context, final String username, final String password, final String accessTokenString, final TokenResponseListener tokenResponseListener) { registrationManager.ensureRegistered(context, new RegistrationListener() { @Override public void onRegistrationFailure(RegistrationStatus error) { @@ -361,10 +380,23 @@ public void onRegistrationFailure(RegistrationStatus error) { @Override public void onRegistrationSuccess() { - oAuthManager.getTokenManager().obtainTokens(username, password, accessTokenString, tokenResponseListener); + oAuthManager.getTokenManager().obtainTokensRoP(username, password, accessTokenString, tokenResponseListener); } }); } + public void signinWithRefreshToken(final Context context, final String refreshToken, final TokenResponseListener tokenResponseListener) { + registrationManager.ensureRegistered(context, new RegistrationListener() { + @Override + public void onRegistrationFailure(RegistrationStatus error) { + logger.error(error.getDescription()); + tokenResponseListener.onAuthorizationFailure(new AuthorizationException(error.getDescription())); + } + @Override + public void onRegistrationSuccess() { + oAuthManager.getTokenManager().obtainTokensRefreshToken(refreshToken, tokenResponseListener); + } + }); + } } diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/ChromeTabActivity.java b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/ChromeTabActivity.java index 8a059f6..c517e26 100644 --- a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/ChromeTabActivity.java +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/ChromeTabActivity.java @@ -14,7 +14,6 @@ package com.ibm.bluemix.appid.android.internal.authorizationmanager; import android.app.Activity; -import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -72,7 +71,7 @@ public void onCreate(Bundle savedInstanceBundle) { CustomTabsIntent customTabsIntent = builder.build(); customTabsIntent.intent.setPackage(AuthorizationUIManager.getPackageNameToUse(this.getApplicationContext())); - customTabsIntent.intent.addFlags(PendingIntent.FLAG_ONE_SHOT); + customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); broadcastReceiver = new BroadcastReceiver() { @Override @@ -88,7 +87,6 @@ public void onReceive(Context context, Intent intent) { Uri uri = Uri.parse(serverUrl); logger.debug("launching custom tab with url: " + uri.toString()); customTabsIntent.launchUrl(this, uri); - } else { //if we launch after authorization completed finish(); @@ -109,7 +107,7 @@ private void onBroadcastReceived(Intent intent) { if (url.startsWith(redirectUrl) && code != null) { logger.debug("Grant code received from authorization server."); - oAuthManager.getTokenManager().obtainTokens(code, authorizationListener); + oAuthManager.getTokenManager().obtainTokensAuthCode(code, authorizationListener); startActivity(clearTopActivityIntent); } else if (url.startsWith(redirectUrl) && error != null) { if (error.equals("invalid_client")) { @@ -118,15 +116,15 @@ private void onBroadcastReceived(Intent intent) { } else { String errorCode = uri.getQueryParameter("error_code"); String errorDescription = uri.getQueryParameter("error_description"); - logger.error("error: " + error); + logger.error("Failed to obtain access and identity tokens, error: " + error); logger.error("errorCode: " + errorCode); logger.error("errorDescription: " + errorDescription); - authorizationListener.onAuthorizationFailure(new AuthorizationException("Failed to obtain access and identity tokens")); + authorizationListener.onAuthorizationFailure(new AuthorizationException(error)); startActivity(clearTopActivityIntent); } } else if (url.startsWith(redirectUrl) && (FORGOT_PASSWORD.equals(flow) || SIGN_UP.equals(flow))) { logger.debug("onBroadcastReceived: end of flow: " + flow); - authorizationListener.onAuthorizationSuccess(null, null); + authorizationListener.onAuthorizationSuccess(null, null, null); startActivity(clearTopActivityIntent); } else { logger.debug("onBroadcastReceived: no match case"); diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/tokenmanager/TokenManager.java b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/tokenmanager/TokenManager.java index 9bd7dca..487fdf1 100644 --- a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/tokenmanager/TokenManager.java +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/tokenmanager/TokenManager.java @@ -21,16 +21,19 @@ import com.ibm.bluemix.appid.android.api.TokenResponseListener; import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.internal.OAuthManager; import com.ibm.bluemix.appid.android.internal.config.Config; import com.ibm.bluemix.appid.android.internal.network.AppIDRequest; import com.ibm.bluemix.appid.android.internal.registrationmanager.RegistrationManager; import com.ibm.bluemix.appid.android.internal.tokens.AccessTokenImpl; import com.ibm.bluemix.appid.android.internal.tokens.IdentityTokenImpl; +import com.ibm.bluemix.appid.android.internal.tokens.RefreshTokenImpl; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.Response; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.ResponseListener; import com.ibm.mobilefirstplatform.clientsdk.android.logger.api.Logger; +import org.json.JSONException; import org.json.JSONObject; import java.security.PrivateKey; @@ -41,22 +44,25 @@ public class TokenManager { private final AppID appId; private final RegistrationManager registrationManager; - private AccessToken latestAccessToken; - private IdentityToken latestIdentityToken; + private IdentityToken latestIdentityToken; + private RefreshToken latestRefreshToken; private static final Logger logger = Logger.getLogger(Logger.INTERNAL_PREFIX + TokenManager.class.getName()); private static final String OAUTH_TOKEN_PATH = "/token"; + private final static String CLIENT_ID = "client_id"; private final static String GRANT_TYPE = "grant_type"; private final static String GRANT_TYPE_AUTH_CODE = "authorization_code"; private final static String CODE = "code"; private final static String REDIRECT_URI = "redirect_uri"; private final static String AUTHORIZATION_HEADER = "Authorization"; - private final static String USERNAME = "username"; - private final static String PASSWORD = "password"; - private final static String GRANT_TYPE_PASSWORD = "password"; + private final static String USERNAME = "username"; + private final static String PASSWORD = "password"; + private final static String GRANT_TYPE_PASSWORD = "password"; + private static final String REFRESH_TOKEN = "refresh_token"; + private static final String GRANT_TYPE_REFRESH = "refresh_token"; private final static String APPID_ACCESS_TOKEN = "appid_access_token"; private final static String ERROR_DESCRIPTION= "error_description"; private final static String ERROR_CODE= "error"; @@ -67,8 +73,8 @@ public TokenManager (OAuthManager oAuthManager) { this.registrationManager = oAuthManager.getRegistrationManager(); } - public void obtainTokens (String code, final AuthorizationListener listener) { - logger.debug("obtainTokens"); + public void obtainTokensAuthCode(String code, final AuthorizationListener listener) { + logger.debug("obtainTokensAuthCode"); String clientId = registrationManager.getRegistrationDataString(RegistrationManager.CLIENT_ID); String redirectUri = registrationManager.getRegistrationDataString(RegistrationManager.REDIRECT_URIS, 0); @@ -127,8 +133,8 @@ public void onSuccess (Response response) { }); } - public void obtainTokens (String username, String password, String accessTokenString, final TokenResponseListener listener) { - logger.debug("obtainTokens - with resource owner password"); + public void obtainTokensRoP(String username, String password, String accessTokenString, final TokenResponseListener listener) { + logger.debug("obtainTokensRoP"); HashMap formParams = new HashMap<>(); formParams.put(USERNAME, username); @@ -140,6 +146,17 @@ public void obtainTokens (String username, String password, String accessTokenSt retrieveTokens(formParams, listener); } + public void obtainTokensRefreshToken(String refreshTokenString, final TokenResponseListener listener) { + logger.debug("obtainTokensRefreshToken"); + if (refreshTokenString == null) { + listener.onAuthorizationFailure(new AuthorizationException("Missing refresh-token")); + } + HashMap formParams = new HashMap<>(); + formParams.put(REFRESH_TOKEN, refreshTokenString); + formParams.put(GRANT_TYPE, GRANT_TYPE_REFRESH); + retrieveTokens(formParams, listener); + } + private String createAuthenticationHeader (String clientId) throws Exception { PrivateKey privateKey = registrationManager.getPrivateKey(); Signature signature = Signature.getInstance("SHA256withRSA"); @@ -159,11 +176,13 @@ private void extractTokens (Response response, TokenResponseListener tokenRespon String idTokenString; AccessToken accessToken; IdentityToken identityToken; + RefreshToken refreshToken = null; logger.debug("Extracting tokens from server response"); + JSONObject responseJSON; try { - JSONObject responseJSON = new JSONObject(response.getResponseText()); + responseJSON = new JSONObject(response.getResponseText()); accessTokenString = responseJSON.getString("access_token"); idTokenString = responseJSON.getString("id_token"); } catch (Exception e){ @@ -189,10 +208,18 @@ private void extractTokens (Response response, TokenResponseListener tokenRespon return; } + try { + String refershTokenString = responseJSON.getString("refresh_token"); + refreshToken = new RefreshTokenImpl(refershTokenString); + } catch (RuntimeException|JSONException e){ + logger.error("Failed to parse refresh_token", e); + } + latestAccessToken = accessToken; latestIdentityToken = identityToken; + latestRefreshToken = refreshToken; - tokenResponseListener.onAuthorizationSuccess(accessToken, identityToken); + tokenResponseListener.onAuthorizationSuccess(accessToken, identityToken, refreshToken); } public AccessToken getLatestAccessToken () { @@ -203,8 +230,13 @@ public IdentityToken getLatestIdentityToken () { return latestIdentityToken; } + public RefreshToken getLatestRefreshToken() { + return latestRefreshToken; + } + public void clearStoredTokens(){ latestAccessToken = null; latestIdentityToken = null; + latestRefreshToken = null; } } diff --git a/lib/src/main/java/com/ibm/bluemix/appid/android/internal/tokens/RefreshTokenImpl.java b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/tokens/RefreshTokenImpl.java new file mode 100644 index 0000000..b639e81 --- /dev/null +++ b/lib/src/main/java/com/ibm/bluemix/appid/android/internal/tokens/RefreshTokenImpl.java @@ -0,0 +1,30 @@ +/* + Copyright 2017 IBM Corp. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.ibm.bluemix.appid.android.internal.tokens; + +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; + +public class RefreshTokenImpl implements RefreshToken { + + private final String raw; + + public RefreshTokenImpl(String raw) { + this.raw = raw; + } + + @Override + public String getRaw() { + return raw; + } +} diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/api/AppIDAuthorizationManager_Test.java b/lib/src/test/java/com/ibm/bluemix/appid/android/api/AppIDAuthorizationManager_Test.java index e1e7a05..1a0a1e0 100644 --- a/lib/src/test/java/com/ibm/bluemix/appid/android/api/AppIDAuthorizationManager_Test.java +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/api/AppIDAuthorizationManager_Test.java @@ -18,11 +18,13 @@ import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.internal.OAuthManager; import com.ibm.bluemix.appid.android.internal.authorizationmanager.AuthorizationManager; import com.ibm.bluemix.appid.android.internal.tokenmanager.TokenManager; import com.ibm.bluemix.appid.android.internal.tokens.AccessTokenImpl; import com.ibm.bluemix.appid.android.internal.tokens.IdentityTokenImpl; +import com.ibm.bluemix.appid.android.internal.tokens.RefreshTokenImpl; import com.ibm.bluemix.appid.android.testing.helpers.Consts; import com.ibm.bluemix.appid.android.testing.mocks.HttpURLConnection_Mock; import com.ibm.mobilefirstplatform.appid_clientsdk_android.BuildConfig; @@ -53,10 +55,15 @@ import java.util.List; import java.util.Map; - import static junit.framework.Assert.assertEquals; -import static org.assertj.core.api.Java6Assertions.*; -import static org.mockito.Mockito.*; +import static junit.framework.Assert.assertNull; +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Java6Assertions.fail; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @RunWith (RobolectricTestRunner.class) @FixMethodOrder (MethodSorters.NAME_ASCENDING) @@ -66,6 +73,7 @@ public class AppIDAuthorizationManager_Test { private AppIDAuthorizationManager appIdAuthManager; private static final AccessToken accessToken = new AccessTokenImpl(Consts.ACCESS_TOKEN); private static final IdentityToken idToken = new IdentityTokenImpl(Consts.ID_TOKEN); + private static final RefreshToken refreshToken = new RefreshTokenImpl(Consts.REFRESH_TOKEN); @Mock private OAuthManager oAuthManagerMock; @Mock private TokenManager tokenManagerMock; @@ -198,12 +206,11 @@ public void obtainAuthorization_test_onAuthorizationSuccess(){ public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); AuthorizationListener authorizationListener = (AuthorizationListener) args[1]; - authorizationListener.onAuthorizationSuccess(accessToken,idToken); + authorizationListener.onAuthorizationSuccess(accessToken,idToken, null); return null; } }).when(authorizationManagerMock).launchAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); - appIdAuthManager.obtainAuthorization(Mockito.mock(Activity.class), new ResponseListener() { @Override public void onSuccess(Response response) { @@ -215,6 +222,8 @@ public void onFailure(Response response, Throwable t, JSONObject extendedInfo) { fail("should get to onSuccess"); } }); + + verify(authorizationManagerMock, times(1)).launchAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); } @Test @@ -229,7 +238,6 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } }).when(authorizationManagerMock).launchAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); - appIdAuthManager.obtainAuthorization(Mockito.mock(Activity.class), new ResponseListener() { @Override public void onSuccess(Response response) { @@ -241,6 +249,8 @@ public void onFailure(Response response, Throwable t, JSONObject extendedInfo) { assertEquals(t.getMessage(), "test exception"); } }); + + verify(authorizationManagerMock, times(1)).launchAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); } @Test @@ -255,7 +265,6 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } }).when(authorizationManagerMock).launchAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); - appIdAuthManager.obtainAuthorization(Mockito.mock(Activity.class), new ResponseListener() { @Override public void onSuccess(Response response) { @@ -267,6 +276,65 @@ public void onFailure(Response response, Throwable t, JSONObject extendedInfo) { assertEquals(t.getMessage(), "Authorization canceled"); } }); + + verify(authorizationManagerMock, times(1)).launchAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); + } + + @Test + public void obtainAuthorization_valid_refreshToken_skipLoginWidget() { + when(tokenManagerMock.getLatestRefreshToken()).thenReturn(new RefreshTokenImpl("SOME_VALID_REFRESH_TOKEN")); + + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + TokenResponseListener tokenResponseListener = (TokenResponseListener) args[1]; + tokenResponseListener.onAuthorizationSuccess(accessToken, idToken, refreshToken); + return null; + } + }).when(tokenManagerMock).obtainTokensRefreshToken(any(String.class), any(TokenResponseListener.class)); + + appIdAuthManager.obtainAuthorization(Mockito.mock(Activity.class), new ResponseListener() { + @Override + public void onSuccess(Response response) { + assertNull(response); + } + + @Override + public void onFailure(Response response, Throwable t, JSONObject extendedInfo) { + fail("should get to onSuccess"); + } + }); + + verify(authorizationManagerMock, never()).launchAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); } + @Test + public void obtainAuthorization_invalid_refreshToken_showLoginWidget() { + when(tokenManagerMock.getLatestRefreshToken()).thenReturn(new RefreshTokenImpl("SOME_INVALID_REFRESH_TOKEN")); + + Mockito.doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + TokenResponseListener tokenResponseListener = (TokenResponseListener) args[1]; + tokenResponseListener.onAuthorizationFailure(new AuthorizationException("invalid grant type")); + return null; + } + }).when(tokenManagerMock).obtainTokensRefreshToken(any(String.class), any(TokenResponseListener.class)); + + appIdAuthManager.obtainAuthorization(Mockito.mock(Activity.class), new ResponseListener() { + @Override + public void onSuccess(Response response) { + assertNull(response); + } + + @Override + public void onFailure(Response response, Throwable t, JSONObject extendedInfo) { + fail("should get to onSuccess"); + } + }); + + verify(authorizationManagerMock, times(1)).launchAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); + } } diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/api/AppID_Test.java b/lib/src/test/java/com/ibm/bluemix/appid/android/api/AppID_Test.java index add3349..4fa710d 100644 --- a/lib/src/test/java/com/ibm/bluemix/appid/android/api/AppID_Test.java +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/api/AppID_Test.java @@ -13,14 +13,13 @@ package com.ibm.bluemix.appid.android.api; -import com.ibm.bluemix.appid.android.api.tokens.AccessToken; -import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; import com.ibm.bluemix.appid.android.internal.OAuthManager; import com.ibm.bluemix.appid.android.internal.loginwidget.LoginWidgetImpl; import com.ibm.bluemix.appid.android.internal.registrationmanager.RegistrationStatus; import com.ibm.bluemix.appid.android.internal.userattributesmanager.UserAttributeManagerImpl; import com.ibm.bluemix.appid.android.testing.helpers.ClassHelper; import com.ibm.bluemix.appid.android.testing.helpers.Consts; +import com.ibm.bluemix.appid.android.testing.helpers.ExceptionMessageMatcher; import com.ibm.mobilefirstplatform.appid_clientsdk_android.BuildConfig; import org.assertj.core.api.ThrowableAssert; @@ -29,11 +28,18 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; +import org.mockito.Mockito; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import static org.assertj.core.api.Java6Assertions.*; +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Java6Assertions.catchThrowable; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; + +import java.util.Locale; @RunWith (RobolectricTestRunner.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -88,11 +94,19 @@ public void call () throws Throwable { } }); + Throwable thrown6 = catchThrowable(new ThrowableAssert.ThrowingCallable() { + @Override + public void call () throws Throwable { + appId.setPreferredLocale(Locale.GERMAN); + } + }); + assertThat(thrown1).hasMessageContaining("AppID is not initialized"); assertThat(thrown2).hasMessageContaining("AppID is not initialized"); assertThat(thrown3).hasMessageContaining("AppID is not initialized"); assertThat(thrown4).hasMessageContaining("AppID is not initialized"); assertThat(thrown5).hasMessageContaining("AppID is not initialized"); + assertThat(thrown6).hasMessageContaining("AppID is not initialized"); } @Test() @@ -107,83 +121,76 @@ public void test02_initialized(){ assertThat(appId.getUserAttributeManager()).isNotNull(); ClassHelper.assertSame(appId.getUserAttributeManager(), UserAttributeManagerImpl.class); - AuthorizationListener listener = new AuthorizationListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertThat(exception.getMessage().equals(RegistrationStatus.FAILED_TO_REGISTER.getDescription())); - } + AuthorizationListener listener = mock(AuthorizationListener.class); - @Override - public void onAuthorizationCanceled() { - assert(false); - } + appId.signinAnonymously(RuntimeEnvironment.application, listener); + appId.signinAnonymously(RuntimeEnvironment.application, "access_token", listener); + appId.signinAnonymously(RuntimeEnvironment.application, "access_token", false, listener); - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - assert(false); - } - }; + appId.setPreferredLocale(Locale.GERMAN); - appId.loginAnonymously(RuntimeEnvironment.application, listener); - appId.loginAnonymously(RuntimeEnvironment.application, "access_token", listener); - appId.loginAnonymously(RuntimeEnvironment.application, "access_token", false, listener); + verifyListenerFailed(3, listener); } @Test public void test03_loginUsingRoP(){ this.appId.initialize(RuntimeEnvironment.application, testTenantId, testRegion); - TokenResponseListener listener = new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertThat(exception.getMessage().equals(RegistrationStatus.FAILED_TO_REGISTER.getDescription())); - } + TokenResponseListener listener = mock(TokenResponseListener.class); - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - assert(false); - } - }; + appId.signinWithResourceOwnerPassword(RuntimeEnvironment.application, "testUsername", "testPassword", listener); - appId.obtainTokensWithROP(RuntimeEnvironment.application, "testUsername", "testPassword", listener); + verifyListenerFailed(1, listener); } @Test public void test04_loginUsingRoPWithNullAccessToken(){ this.appId.initialize(RuntimeEnvironment.application, testTenantId, testRegion); - TokenResponseListener listener = new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertThat(exception.getMessage().equals(RegistrationStatus.FAILED_TO_REGISTER.getDescription())); - } + TokenResponseListener listener = mock(TokenResponseListener.class); - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - assert(false); - } - }; + appId.signinWithResourceOwnerPassword(RuntimeEnvironment.application, "testUsername", "testPassword", listener, null); - appId.obtainTokensWithROP(RuntimeEnvironment.application, "testUsername", "testPassword", listener, null); + verifyListenerFailed(1, listener); } @Test public void test05_loginUsingRoPWithAccessToken(){ this.appId.initialize(RuntimeEnvironment.application, testTenantId, testRegion); - TokenResponseListener listener = new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertThat(exception.getMessage().equals(RegistrationStatus.FAILED_TO_REGISTER.getDescription())); - } + TokenResponseListener listener = mock(TokenResponseListener.class); - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - assert(false); - } - }; + appId.signinWithResourceOwnerPassword(RuntimeEnvironment.application, "testUsername", "testPassword", listener, Consts.ACCESS_TOKEN); + + verifyListenerFailed(1, listener); + } + + @Test + public void testRefreshTokensFailed(){ + this.appId.initialize(RuntimeEnvironment.application, testTenantId, testRegion); + + TokenResponseListener listener = mock(TokenResponseListener.class); + + appId.signinWithRefreshToken(RuntimeEnvironment.application, "refreshToken", listener); + + verifyListenerFailed(1, listener); + } + + @Test + public void testRefreshTokensLatestFailed(){ + this.appId.initialize(RuntimeEnvironment.application, testTenantId, testRegion); + + TokenResponseListener listener = mock(TokenResponseListener.class); + + appId.signinWithRefreshToken(RuntimeEnvironment.application, listener); + + ExceptionMessageMatcher matcher = new ExceptionMessageMatcher<>("Missing refresh-token"); + Mockito.verify(listener).onAuthorizationFailure(argThat(matcher)); + } - appId.obtainTokensWithROP(RuntimeEnvironment.application, "testUsername", "testPassword", listener, Consts.ACCESS_TOKEN); + private void verifyListenerFailed(int wantedNumberOfInvocations, TokenResponseListener listener) { + ExceptionMessageMatcher matcher = new ExceptionMessageMatcher<>(RegistrationStatus.FAILED_TO_REGISTER.getDescription()); + Mockito.verify(listener, times(wantedNumberOfInvocations)).onAuthorizationFailure(argThat(matcher)); } } diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationManager_Test.java b/lib/src/test/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationManager_Test.java index b1b108d..5024033 100644 --- a/lib/src/test/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationManager_Test.java +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationManager_Test.java @@ -22,6 +22,7 @@ import com.ibm.bluemix.appid.android.api.TokenResponseListener; import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.internal.OAuthManager; import com.ibm.bluemix.appid.android.internal.network.AppIDRequest; import com.ibm.bluemix.appid.android.internal.network.AppIDRequestFactory; @@ -32,6 +33,7 @@ import com.ibm.bluemix.appid.android.internal.tokens.AccessTokenImpl; import com.ibm.bluemix.appid.android.internal.tokens.IdentityTokenImpl; import com.ibm.bluemix.appid.android.testing.helpers.Consts; +import com.ibm.bluemix.appid.android.testing.helpers.ExceptionMessageMatcher; import com.ibm.mobilefirstplatform.appid_clientsdk_android.BuildConfig; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.Response; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.ResponseListener; @@ -44,6 +46,7 @@ import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -54,12 +57,14 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -91,6 +96,7 @@ public class AuthorizationManager_Test { private Activity mockActivity; @Mock private IdentityToken mockIdToken; + private Locale defaultLocale = Locale.getDefault(); private AuthorizationManager authManager; private String username = "testUser"; private String password = "testPassword"; @@ -201,22 +207,22 @@ public void before() { public Void answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); TokenResponseListener tokenListener = (TokenResponseListener) args[3]; - tokenListener.onAuthorizationSuccess(expectedAccessToken, expectedIdToken); + tokenListener.onAuthorizationSuccess(expectedAccessToken, expectedIdToken, null); return null; } } - ).when(tokenManagerMock).obtainTokens(eq(username), eq(password), eq(passedAccessToken), any(TokenResponseListener.class)); + ).when(tokenManagerMock).obtainTokensRoP(eq(username), eq(password), eq(passedAccessToken), any(TokenResponseListener.class)); doAnswer(new Answer() { @Override public Void answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); AuthorizationListener authListener = (AuthorizationListener) args[1]; - authListener.onAuthorizationSuccess(expectedAccessToken, expectedIdToken); + authListener.onAuthorizationSuccess(expectedAccessToken, expectedIdToken, null); return null; } } - ).when(tokenManagerMock).obtainTokens(anyString(), any(AuthorizationListener.class)); + ).when(tokenManagerMock).obtainTokensAuthCode(anyString(), any(AuthorizationListener.class)); authManager = new AuthorizationManager(oAuthManagerMock, mockContext); appidMock.overrideOAuthServerHost = null; @@ -236,14 +242,14 @@ public Void answer(InvocationOnMock invocation) { }).when(registrationManager).ensureRegistered(eq(mockContext), any(RegistrationListener.class)); - authManager.obtainTokensWithROP(mockContext, username, password, passedAccessToken, new TokenResponseListener() { + authManager.signinWithResourceOwnerPassword(mockContext, username, password, passedAccessToken, new TokenResponseListener() { @Override public void onAuthorizationFailure(AuthorizationException exception) { assertEquals(exception.getMessage(), RegistrationStatus.NOT_REGISTRED.getDescription()); } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } }); @@ -261,14 +267,14 @@ public Void answer(InvocationOnMock invocation) { } ).when(registrationManager).ensureRegistered(eq(mockContext), any(RegistrationListener.class)); - authManager.obtainTokensWithROP(mockContext, username, password, passedAccessToken, new TokenResponseListener() { + authManager.signinWithResourceOwnerPassword(mockContext, username, password, passedAccessToken, new TokenResponseListener() { @Override public void onAuthorizationFailure(AuthorizationException exception) { fail("should get to onAuthorizationSuccess"); } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { assertEquals(accessToken.getRaw(), expectedAccessToken.getRaw()); assertEquals(identityToken.getRaw(), expectedIdToken.getRaw()); } @@ -299,7 +305,7 @@ public Void answer(InvocationOnMock invocation) { } ).when(mockRequest).send(any(ResponseListener.class)); - authManager.loginAnonymously(mockContext, expectedAccessToken.getRaw(), true, new AuthorizationListener() { + authManager.signinAnonymously(mockContext, expectedAccessToken.getRaw(), true, new AuthorizationListener() { @Override public void onAuthorizationCanceled() { fail("should get to onAuthorizationSuccess"); @@ -311,7 +317,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { assertEquals(accessToken.getRaw(), expectedAccessToken.getRaw()); assertEquals(identityToken.getRaw(), expectedIdToken.getRaw()); } @@ -331,7 +337,7 @@ public Void answer(InvocationOnMock invocation) { ).when(registrationManager).ensureRegistered(eq(mockContext), any(RegistrationListener.class)); - authManager.loginAnonymously(mockContext, expectedAccessToken.getRaw(), true, new AuthorizationListener() { + authManager.signinAnonymously(mockContext, expectedAccessToken.getRaw(), true, new AuthorizationListener() { @Override public void onAuthorizationCanceled() { fail("should get to onAuthorizationFailure"); @@ -343,7 +349,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } }); @@ -373,7 +379,7 @@ public Void answer(InvocationOnMock invocation) { } ).when(mockRequest).send(any(ResponseListener.class)); - authManager.loginAnonymously(mockContext, expectedAccessToken.getRaw(), true, new AuthorizationListener() { + authManager.signinAnonymously(mockContext, expectedAccessToken.getRaw(), true, new AuthorizationListener() { @Override public void onAuthorizationCanceled() { fail("should get to onAuthorizationFailure"); @@ -385,7 +391,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } }); @@ -411,7 +417,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -440,14 +446,14 @@ public Void answer(InvocationOnMock invocation) { spyAuthManager.launchAuthorizationUI(mockActivity, new AuthorizationListener() { @Override public void onAuthorizationFailure(AuthorizationException exception) { - String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/authorization?response_type=code&client_id=null&redirect_uri=null&scope=openid"; + String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/authorization?response_type=code&client_id=null&redirect_uri=null&scope=openid&language="+defaultLocale; assertEquals(exception.getMessage(), "Could NOT find installed browser that support Chrome tabs on the device."); verify(spyAuthManager).createAuthorizationUIManager(any(OAuthManager.class), any(AuthorizationListener.class), eq(expectedAuthUrl), anyString()); } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -458,6 +464,42 @@ public void onAuthorizationCanceled() { }); } + + @Test + public void launchAuthorizationUI_localeTest(){ + final Locale overrideLocale = Locale.FRENCH; + final AuthorizationManager spyAuthManager = spy(authManager); + doAnswer(new Answer() { + public Void answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + RegistrationListener regListener = (RegistrationListener) args[1]; + regListener.onRegistrationSuccess(); + return null; + } + } + ).when(registrationManager).ensureRegistered(eq(mockActivity), any(RegistrationListener.class)); + + when(mockActivity.getApplicationContext()).thenReturn(mockContext); + + spyAuthManager.setPreferredLocale(overrideLocale); + + AuthorizationListener listener = Mockito.mock(AuthorizationListener.class); + + doAnswer(new Answer() { + public Void answer(InvocationOnMock invocation) { + String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/authorization?response_type=code&client_id=null&redirect_uri=null&scope=openid&language="+overrideLocale; + verify(spyAuthManager).createAuthorizationUIManager(any(OAuthManager.class), any(AuthorizationListener.class), eq(expectedAuthUrl), anyString()); + return null; + } + }).when(listener).onAuthorizationFailure(any(AuthorizationException.class)); + + spyAuthManager.launchAuthorizationUI(mockActivity, listener); + + ExceptionMessageMatcher matcher = new ExceptionMessageMatcher<>("Could NOT find installed browser that support Chrome tabs on the device."); + verify(listener).onAuthorizationFailure(argThat(matcher)); + } + + @Test public void launchSignUpAuthorizationUI_failure(){ Activity activity = new Activity(); @@ -478,7 +520,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -507,13 +549,13 @@ public Void answer(InvocationOnMock invocation) { spyAuthManager.launchSignUpAuthorizationUI(mockActivity, new AuthorizationListener() { @Override public void onAuthorizationFailure(AuthorizationException exception) { - String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/authorization?response_type=sign_up&client_id=null&redirect_uri=null&scope=openid"; + String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/authorization?response_type=sign_up&client_id=null&redirect_uri=null&scope=openid&language="+defaultLocale; assertEquals(exception.getMessage(), "Could NOT find installed browser that support Chrome tabs on the device."); verify(spyAuthManager).createAuthorizationUIManager(any(OAuthManager.class), any(AuthorizationListener.class), eq(expectedAuthUrl), anyString()); } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -536,7 +578,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -560,7 +602,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -586,13 +628,13 @@ public void launchChangePasswordUI_success() throws Exception { spyAuthManager.launchChangePasswordUI(mockActivity, new AuthorizationListener() { @Override public void onAuthorizationFailure(AuthorizationException exception) { - String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/cloud_directory/change_password?user_id=1234"; + String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/cloud_directory/change_password?user_id=1234&language="+defaultLocale; assertEquals(exception.getMessage(), "Could NOT find installed browser that support Chrome tabs on the device."); verify(spyAuthManager).createAuthorizationUIManager(any(OAuthManager.class), any(AuthorizationListener.class), eq(expectedAuthUrl), anyString()); } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -621,7 +663,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -644,7 +686,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -668,7 +710,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -705,13 +747,13 @@ public Void answer(InvocationOnMock invocation) { spyAuthManager.launchChangeDetailsUI(mockActivity, new AuthorizationListener() { @Override public void onAuthorizationFailure(AuthorizationException exception) { - String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/cloud_directory/change_details?code=1234"; + String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/cloud_directory/change_details?code=1234&language="+defaultLocale; assertEquals(exception.getMessage(), "Could NOT find installed browser that support Chrome tabs on the device."); verify(spyAuthManager).createAuthorizationUIManager(any(OAuthManager.class), any(AuthorizationListener.class), eq(expectedAuthUrl), anyString()); } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -750,7 +792,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -792,7 +834,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -836,7 +878,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -879,7 +921,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @@ -907,13 +949,13 @@ public Void answer(InvocationOnMock invocation) { when(mockActivity.getApplicationContext()).thenReturn(mockContext); spyAuthManager.launchForgotPasswordUI(mockActivity, new AuthorizationListener() { @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } @Override public void onAuthorizationFailure(AuthorizationException exception) { - String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/cloud_directory/forgot_password"; + String expectedAuthUrl = "https://appid-oauth.stubPrefix/oauth/v3/null/cloud_directory/forgot_password?language="+defaultLocale; assertEquals(exception.getMessage(), "Could NOT find installed browser that support Chrome tabs on the device."); verify(spyAuthManager).createAuthorizationUIManager(any(OAuthManager.class), any(AuthorizationListener.class), eq(expectedAuthUrl), anyString()); } diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationUIManager_Test.java b/lib/src/test/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationUIManager_Test.java index c18ca82..01bb6fe 100644 --- a/lib/src/test/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationUIManager_Test.java +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/internal/authorizationmanager/AuthorizationUIManager_Test.java @@ -24,6 +24,7 @@ import com.ibm.bluemix.appid.android.api.AuthorizationListener; import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.internal.OAuthManager; import org.junit.Before; @@ -101,7 +102,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { fail("should get to onAuthorizationFailure"); } }; diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/internal/loginwidget/LoginWidgetImpl_Test.java b/lib/src/test/java/com/ibm/bluemix/appid/android/internal/loginwidget/LoginWidgetImpl_Test.java index 756c544..a422191 100644 --- a/lib/src/test/java/com/ibm/bluemix/appid/android/internal/loginwidget/LoginWidgetImpl_Test.java +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/internal/loginwidget/LoginWidgetImpl_Test.java @@ -18,6 +18,7 @@ import com.ibm.bluemix.appid.android.api.AuthorizationListener; import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.internal.OAuthManager; import com.ibm.bluemix.appid.android.internal.authorizationmanager.AuthorizationManager; import com.ibm.bluemix.appid.android.internal.tokenmanager.TokenManager; @@ -73,7 +74,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); AccessToken accessToken = (AccessToken) args[1]; AuthorizationListener authorizationListener = (AuthorizationListener) args[2]; - authorizationListener.onAuthorizationSuccess(accessToken, null); + authorizationListener.onAuthorizationSuccess(accessToken, null, null); return null; } }).when(mockAuthManager).launchAuthorizationUI(any(Activity.class), any(AccessToken.class),any(AuthorizationListener.class)); @@ -91,7 +92,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { assertEquals(accessToken, expectedAccessToken); } }, null); @@ -106,7 +107,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); AccessToken accessToken = (AccessToken) args[1]; AuthorizationListener authorizationListener = (AuthorizationListener) args[2]; - authorizationListener.onAuthorizationSuccess(accessToken, null); + authorizationListener.onAuthorizationSuccess(accessToken, null, null); return null; } }).when(mockAuthManager).launchAuthorizationUI(any(Activity.class), any(AccessToken.class),any(AuthorizationListener.class)); @@ -124,7 +125,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { assertEquals(accessToken.getRaw(), expectedAccessToken.getRaw()); } }, expectedAccessToken.getRaw()); @@ -139,7 +140,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); AccessToken accessToken = expectedAccessToken; AuthorizationListener authorizationListener = (AuthorizationListener) args[1]; - authorizationListener.onAuthorizationSuccess(accessToken, null); + authorizationListener.onAuthorizationSuccess(accessToken, null, null); return null; } }).when(mockAuthManager).launchSignUpAuthorizationUI(any(Activity.class), any(AuthorizationListener.class)); @@ -157,7 +158,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { assertEquals(accessToken, expectedAccessToken); } }); @@ -172,7 +173,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); AccessToken accessToken = expectedAccessToken; AuthorizationListener authorizationListener = (AuthorizationListener) args[1]; - authorizationListener.onAuthorizationSuccess(accessToken, null); + authorizationListener.onAuthorizationSuccess(accessToken, null, null); return null; } }).when(mockAuthManager).launchChangePasswordUI(any(Activity.class), any(AuthorizationListener.class)); @@ -190,7 +191,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { assertEquals(accessToken, expectedAccessToken); } }); @@ -205,7 +206,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); AccessToken accessToken = expectedAccessToken; AuthorizationListener authorizationListener = (AuthorizationListener) args[1]; - authorizationListener.onAuthorizationSuccess(accessToken, null); + authorizationListener.onAuthorizationSuccess(accessToken, null, null); return null; } }).when(mockAuthManager).launchChangeDetailsUI(any(Activity.class), any(AuthorizationListener.class)); @@ -223,7 +224,7 @@ public void onAuthorizationFailure(AuthorizationException exception) { } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { assertEquals(accessToken, expectedAccessToken); } }); @@ -237,7 +238,7 @@ public void launchForgotPassword_test(){ public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); AuthorizationListener forgotPasswordListener = (AuthorizationListener) args[1]; - forgotPasswordListener.onAuthorizationSuccess(null, null); + forgotPasswordListener.onAuthorizationSuccess(null, null, null); return null; } }).when(mockAuthManager).launchForgotPasswordUI(any(Activity.class), any(AuthorizationListener.class)); @@ -245,7 +246,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { loginWidget.launchForgotPassword(Mockito.mock(Activity.class), new AuthorizationListener() { @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { assert(true); } diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/internal/tokenmanager/TokenManager_Test.java b/lib/src/test/java/com/ibm/bluemix/appid/android/internal/tokenmanager/TokenManager_Test.java index cc772f9..03d8d8f 100644 --- a/lib/src/test/java/com/ibm/bluemix/appid/android/internal/tokenmanager/TokenManager_Test.java +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/internal/tokenmanager/TokenManager_Test.java @@ -15,9 +15,9 @@ import com.ibm.bluemix.appid.android.api.AppID; import com.ibm.bluemix.appid.android.api.AuthorizationException; import com.ibm.bluemix.appid.android.api.AuthorizationListener; -import com.ibm.bluemix.appid.android.api.TokenResponseListener; import com.ibm.bluemix.appid.android.api.tokens.AccessToken; import com.ibm.bluemix.appid.android.api.tokens.IdentityToken; +import com.ibm.bluemix.appid.android.api.tokens.RefreshToken; import com.ibm.bluemix.appid.android.internal.OAuthManager; import com.ibm.bluemix.appid.android.internal.network.AppIDRequest; import com.ibm.bluemix.appid.android.internal.preferences.JSONPreference; @@ -25,39 +25,44 @@ import com.ibm.bluemix.appid.android.internal.registrationmanager.RegistrationManager; import com.ibm.bluemix.appid.android.internal.tokens.AccessTokenImpl; import com.ibm.bluemix.appid.android.internal.tokens.IdentityTokenImpl; +import com.ibm.bluemix.appid.android.internal.tokens.RefreshTokenImpl; import com.ibm.bluemix.appid.android.testing.helpers.Consts; +import com.ibm.bluemix.appid.android.testing.mocks.Response_Mock; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.Response; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.ResponseListener; +import org.codehaus.plexus.util.StringUtils; +import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; +import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import org.powermock.core.classloader.annotations.PrepareForTest; import org.robolectric.RobolectricTestRunner; -import java.io.InputStream; import java.math.BigInteger; -import java.security.Signature; import java.security.interfaces.RSAPrivateKey; -import java.util.List; import java.util.Map; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) @@ -82,6 +87,7 @@ public class TokenManager_Test { private String stubRedirectUri = "http://stub"; private static final AccessToken expectedAccessToken = new AccessTokenImpl(Consts.ACCESS_TOKEN); private static final IdentityToken expectedIdToken = new IdentityTokenImpl(Consts.ID_TOKEN); + private static final RefreshToken expectedRefreshToken = new RefreshTokenImpl(Consts.REFRESH_TOKEN); private Response testReponse; @Before @@ -128,47 +134,33 @@ public BigInteger getModulus() { @Test public void obtainTokensRop_success() { + testReponse = createResponse(); - testReponse = new Response() { - @Override - public String getRequestURL() { - return null; - } - - @Override - public int getStatus() { - return 200; - } - - @Override - public String getResponseText() { - return "{\"access_token\": " + expectedAccessToken.getRaw() +", " + - "\"id_token\":" + expectedIdToken.getRaw() + "}"; - } - + doAnswer(new Answer() { @Override - public JSONObject getResponseJSON() { + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + ResponseListener responseListener = (ResponseListener) args[1]; + responseListener.onSuccess(testReponse); return null; } + }).when(stubRequest).send(any(Map.class), any(ResponseListener.class)); - @Override - public byte[] getResponseBytes() { - return new byte[0]; - } + spyTokenManager.obtainTokensRoP(username, password, Consts.ACCESS_TOKEN, getExpectedSuccessListener()); + } - @Override - public InputStream getResponseByteStream() { - return null; - } + @Test + public void obtainTokensRefreshToken_success() { + final String accessToken = expectedAccessToken.getRaw(); + final String idToken = expectedIdToken.getRaw(); + final String refreshToken = expectedRefreshToken.getRaw(); - @Override - public long getContentLength() { - return 0; - } + testReponse = createResponse(createTokensResponseText(accessToken, idToken, refreshToken), 200); + ArgumentMatcher> formParametersIncludeRefreshTokenMatcher = new ArgumentMatcher>() { @Override - public Map> getHeaders() { - return null; + public boolean matches(Object argument) { + return ((Map) argument).containsKey("refresh_token"); } }; @@ -180,67 +172,56 @@ public Object answer(InvocationOnMock invocation) throws Throwable { responseListener.onSuccess(testReponse); return null; } - }).when(stubRequest).send(any(Map.class), any(ResponseListener.class)); + }).when(stubRequest).send(argThat(formParametersIncludeRefreshTokenMatcher), any(ResponseListener.class)); - spyTokenManager.obtainTokens(username, password, Consts.ACCESS_TOKEN, new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - fail("should get to onAuthorizationSuccess"); - } + // obtain tokens with refresh, should store the retrieved tokens (incl. refresh) + spyTokenManager.obtainTokensRefreshToken(refreshToken, getExpectedSuccessListener(accessToken, idToken, refreshToken)); - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - assertEquals(accessToken.getRaw(), expectedAccessToken.getRaw()); - assertEquals(identityToken.getRaw(), expectedIdToken.getRaw()); - } - }); - } + verify(stubRequest, times(1)).send(argThat(formParametersIncludeRefreshTokenMatcher), any(ResponseListener.class)); - @Test - public void obtainTokensRop_failure() { + RefreshToken latestRefreshToken = spyTokenManager.getLatestRefreshToken(); + assertNotNull(latestRefreshToken); + assertEquals(refreshToken, latestRefreshToken.getRaw()); - final String testDescription = "test description error123"; - testReponse = new Response() { - @Override - public String getRequestURL() { - return null; - } + AccessToken latestAccessToken = spyTokenManager.getLatestAccessToken(); + assertNotNull(latestAccessToken); + assertEquals(accessToken, latestAccessToken.getRaw()); - @Override - public int getStatus() { - return 400; - } + IdentityToken latestIdToken = spyTokenManager.getLatestIdentityToken(); + assertNotNull(latestIdToken); + assertEquals(idToken, latestIdToken.getRaw()); + } - @Override - public String getResponseText() { - return "{\"error\": \"invalid_grant\" , \"error_description\": \"" + testDescription + "\" }"; - } + @Test + public void obtainTokensRefreshToken_failure() { + final String testDescription = "invalid refresh token"; + testReponse = createResponse("{\"error\": \"invalid_grant\" , \"error_description\": \"" + testDescription + "\" }", 400); + doAnswer(new Answer() { @Override - public JSONObject getResponseJSON() { + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + ResponseListener responseListener = (ResponseListener) args[1]; + responseListener.onFailure(testReponse ,null, null); return null; } + }).when(stubRequest).send(any(Map.class), any(ResponseListener.class)); - @Override - public byte[] getResponseBytes() { - return new byte[0]; - } + spyTokenManager.obtainTokensRefreshToken(expectedRefreshToken.getRaw(), getExpectedFailureListener(testDescription)); + } - @Override - public InputStream getResponseByteStream() { - return null; - } + private Response createResponse() { + return createResponse(createExpectedTokensResponse(), 200); + } - @Override - public long getContentLength() { - return 0; - } + private Response createResponse(String responseText, int code) { + return new Response_Mock(responseText, code); + } - @Override - public Map> getHeaders() { - return null; - } - }; + @Test + public void obtainTokensRop_failure() { + final String testDescription = "test description error123"; + testReponse = createResponse("{\"error\": \"invalid_grant\" , \"error_description\": \"" + testDescription + "\" }", 400); doAnswer(new Answer() { @Override @@ -252,32 +233,11 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } }).when(stubRequest).send(any(Map.class), any(ResponseListener.class)); - spyTokenManager.obtainTokens(username, password, null, new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertEquals(exception.getMessage(), testDescription); - } + spyTokenManager.obtainTokensRoP(username, password, null, getExpectedFailureListener(testDescription)); - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - fail("should get to onAuthorizationFailure"); - - } - }); //test the exception parsing testReponse = null; - spyTokenManager.obtainTokens(username, password, null, new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertEquals(exception.getMessage(), "Failed to retrieve tokens" ); - } - - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - fail("should get to onAuthorizationFailure"); - - } - }); + spyTokenManager.obtainTokensRoP(username, password, null, getExpectedFailureListener("Failed to retrieve tokens")); } @Test @@ -294,178 +254,19 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } }).when(stubRequest).send(any(Map.class), any(ResponseListener.class)); - spyTokenManager.obtainTokens(username, password, null, new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertEquals(exception.getMessage(), "Failed to parse server response"); - } - - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - fail("should get to onAuthorizationFailure"); - - } - }); + spyTokenManager.obtainTokensRoP(username, password, null, getExpectedFailureListener("Failed to parse server response")); //bad access token - testReponse = new Response() { - @Override - public String getRequestURL() { - return null; - } - - @Override - public int getStatus() { - return 200; - } - - @Override - public String getResponseText() { - return "{\"access_token\": " + "\"bad access token\"" + ", " + - "\"id_token\":" + expectedIdToken.getRaw() + "}"; - } - - @Override - public JSONObject getResponseJSON() { - return null; - } - - @Override - public byte[] getResponseBytes() { - return new byte[0]; - } - - @Override - public InputStream getResponseByteStream() { - return null; - } - - @Override - public long getContentLength() { - return 0; - } - - @Override - public Map> getHeaders() { - return null; - } - }; - spyTokenManager.obtainTokens(username, password, null, new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertEquals(exception.getMessage(), "Failed to parse access_token"); - } - - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - fail("should get to onAuthorizationFailure"); - - } - }); + testReponse = createResponse(createTokensResponseText("bad access token", expectedIdToken.getRaw()), 400); + spyTokenManager.obtainTokensRoP(username, password, null, getExpectedFailureListener("Failed to parse access_token")); //bad id token - testReponse = new Response() { - @Override - public String getRequestURL() { - return null; - } - - @Override - public int getStatus() { - return 200; - } - - @Override - public String getResponseText() { - return "{\"access_token\": " + expectedAccessToken.getRaw() + ", " + - "\"id_token\":" + "\"bad Id token\"" + "}"; - } - - @Override - public JSONObject getResponseJSON() { - return null; - } - - @Override - public byte[] getResponseBytes() { - return new byte[0]; - } - - @Override - public InputStream getResponseByteStream() { - return null; - } - - @Override - public long getContentLength() { - return 0; - } - - @Override - public Map> getHeaders() { - return null; - } - }; - - spyTokenManager.obtainTokens(username, password, null, new TokenResponseListener() { - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - assertEquals(exception.getMessage(), "Failed to parse id_token"); - } + testReponse = createResponse(createTokensResponseText(expectedAccessToken.getRaw(), "bad Id token"), 400); - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - fail("should get to onAuthorizationFailure"); - - } - }); + spyTokenManager.obtainTokensRoP(username, password, null, getExpectedFailureListener("Failed to parse id_token")); } - - @Test public void obtainTokens_Authorization_Code_success() { - - testReponse = new Response() { - @Override - public String getRequestURL() { - return null; - } - - @Override - public int getStatus() { - return 200; - } - - @Override - public String getResponseText() { - return "{\"access_token\": " + expectedAccessToken.getRaw() +", " + - "\"id_token\":" + expectedIdToken.getRaw() + "}"; - } - - @Override - public JSONObject getResponseJSON() { - return null; - } - - @Override - public byte[] getResponseBytes() { - return new byte[0]; - } - - @Override - public InputStream getResponseByteStream() { - return null; - } - - @Override - public long getContentLength() { - return 0; - } - - @Override - public Map> getHeaders() { - return null; - } - }; + testReponse = createResponse(); doAnswer(new Answer() { @Override @@ -477,97 +278,99 @@ public Object answer(InvocationOnMock invocation) throws Throwable { } }).when(stubRequest).send(any(Map.class), any(ResponseListener.class)); - spyTokenManager.obtainTokens("Some Code", new AuthorizationListener() { - @Override - public void onAuthorizationCanceled() { - fail("should get to onAuthorizationSuccess"); - } - - @Override - public void onAuthorizationFailure(AuthorizationException exception) { - fail("should get to onAuthorizationSuccess"); - } - - @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - assertEquals(accessToken.getRaw(), expectedAccessToken.getRaw()); - assertEquals(identityToken.getRaw(), expectedIdToken.getRaw()); - } - }); + spyTokenManager.obtainTokensAuthCode("Some Code", getExpectedSuccessListener()); } @Test public void obtainTokens_Authorization_Code_failure() { + testReponse = createResponse(); - testReponse = new Response() { + doAnswer(new Answer() { @Override - public String getRequestURL() { + public Object answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + ResponseListener responseListener = (ResponseListener) args[1]; + responseListener.onFailure(null, null, null); return null; } + }).when(stubRequest).send(any(Map.class), any(ResponseListener.class)); - @Override - public int getStatus() { - return 200; - } - - @Override - public String getResponseText() { - return "{\"access_token\": " + expectedAccessToken.getRaw() +", " + - "\"id_token\":" + expectedIdToken.getRaw() + "}"; - } - - @Override - public JSONObject getResponseJSON() { - return null; - } + spyTokenManager.obtainTokensAuthCode("Some Code", getExpectedFailureListener("Failed to retrieve tokens")); + } + private AuthorizationListener getExpectedFailureListener(final String expectedErrorMessage) { + return new AuthorizationListener() { @Override - public byte[] getResponseBytes() { - return new byte[0]; + public void onAuthorizationCanceled() { + fail("should get to onAuthorizationFailure"); } @Override - public InputStream getResponseByteStream() { - return null; + public void onAuthorizationFailure(AuthorizationException exception) { + assertEquals(expectedErrorMessage, exception.getMessage()); } @Override - public long getContentLength() { - return 0; - } + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + fail("should get to onAuthorizationFailure"); - @Override - public Map> getHeaders() { - return null; } }; + } - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - ResponseListener responseListener = (ResponseListener) args[1]; - responseListener.onFailure(null, null, null); - return null; - } - }).when(stubRequest).send(any(Map.class), any(ResponseListener.class)); + private AuthorizationListener getExpectedSuccessListener() { + return getExpectedSuccessListener(expectedAccessToken.getRaw(), expectedIdToken.getRaw(), null); + } - spyTokenManager.obtainTokens("Some Code", new AuthorizationListener() { + private AuthorizationListener getExpectedSuccessListener(final String expAccessToken, final String expIdToken, final String expRefreshToken) { + return new AuthorizationListener() { @Override public void onAuthorizationCanceled() { - fail("should get to onAuthorizationFailure"); + fail("should get to onAuthorizationSuccess"); } @Override public void onAuthorizationFailure(AuthorizationException exception) { - assertEquals(exception.getMessage(), "Failed to retrieve tokens"); + fail("should get to onAuthorizationSuccess"); } @Override - public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) { - fail("should get to onAuthorizationFailure"); - + public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) { + assertEquals(expAccessToken, accessToken.getRaw()); + assertEquals(expIdToken, identityToken.getRaw()); + if (expRefreshToken != null) { + assertEquals(expRefreshToken, refreshToken.getRaw()); + } } - }); + }; + } + + private String createExpectedTokensResponse() { + return createTokensResponseText(expectedAccessToken.getRaw(), expectedIdToken.getRaw(), expectedRefreshToken.getRaw()); + } + + private String createTokensResponseText(String accessToken, String idToken) { + return createTokensResponseText(accessToken, idToken, null); + } + + private String createTokensResponseText(String accessToken, String idToken, String refreshToken) { + JSONObject params = new JSONObject(); + try { + params.put("access_token", accessToken); + params.put("id_token", idToken); + if (!StringUtils.isEmpty(refreshToken)) { + params.put("refresh_token", refreshToken); + } + } catch (JSONException e) { + e.printStackTrace(); + } + return params.toString(); + } + + private static class A extends ArgumentMatcher { + @Override + public boolean matches(Object argument) { + return false; + } } -} +} \ No newline at end of file diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/testing/helpers/Consts.java b/lib/src/test/java/com/ibm/bluemix/appid/android/testing/helpers/Consts.java index 9053073..dd14096 100644 --- a/lib/src/test/java/com/ibm/bluemix/appid/android/testing/helpers/Consts.java +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/testing/helpers/Consts.java @@ -16,4 +16,5 @@ public class Consts { public static final String ACCESS_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpPU0UifQ.eyJpc3MiOiJpbWYtYXV0aHNlcnZlci5zdGFnZTEubXlibHVlbWl4Lm5ldCIsImV4cCI6MTQ4OTk1NzQ1OSwiYXVkIjoiNDA4ZWIzNmEyYTA2OWFkODljZDE5Yzc4OWE5NmI3Y2YzNmI1NTBlYyIsInN1YiI6IjA5YjdmZWE1LTJlNGUtNDBiOC05ZDgxLWRmNTAwNzFhMzA1MyIsImFtciI6WyJmYWNlYm9vayJdLCJpYXQiOjE0ODczNjU0NTksInRlbmFudCI6IjUwZDBiZWVkLWFkZDctNDhkZC04YjBhLWM4MThjYjQ1NmJiNCIsInNjb3BlIjoiYXBwaWRfZGVmYXVsdCBhcHBpZF9yZWFkcHJvZmlsZSBhcHBpZF9yZWFkdXNlcmF0dHIgYXBwaWRfd3JpdGV1c2VyYXR0ciJ9.gQq4_IxbkPg1FsVZiiTqsejURL4E_Ijr8U1vDob-06GcsorVijS7HHf0kgWD84cDNa6z4Lp7HkmvI8vmiUIfV6ch-xJS_LSJphKy5nZxXqVHchRDJAMUNMiAYqC5ohZ4MXmjuGFIrVl1iZdTyP5Oz-5e6UzDccdAGkPokNs_IyXwiSmGWF5fOKSgfqANYwRBaC-JeXlzEcVZ697q92kiErBNl3ziuSFWxss86ZHHiKdLoHUpkDRKgPHwSQmE_Kwzj8v8Td9WuIVwXCF-D4koTuPJSe2aPqCLuV28PE9wRh5j3sFraKbQIcjuHuiAd5KBhzwaeVT20_0zrgyr3QG0Vg"; public static final String ID_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpPU0UifQ.eyJpc3MiOiJpbWYtYXV0aHNlcnZlci5zdGFnZTEubXlibHVlbWl4Lm5ldCIsImF1ZCI6IjQwOGViMzZhMmEwNjlhZDg5Y2QxOWM3ODlhOTZiN2NmMzZiNTUwZWMiLCJleHAiOjE0ODk5NTc0NTksInRlbmFudCI6IjUwZDBiZWVkLWFkZDctNDhkZC04YjBhLWM4MThjYjQ1NmJiNCIsImlhdCI6MTQ4NzM2NTQ1OSwiZW1haWwiOiJkb25sb25xd2VydHlAZ21haWwuY29tIiwibmFtZSI6IkRvbiBMb24iLCJwaWN0dXJlIjoiaHR0cHM6Ly9zY29udGVudC54eC5mYmNkbi5uZXQvdi90MS4wLTEvcDUweDUwLzEzNTAxNTUxXzI4NjQwNzgzODM3ODg5Ml8xNzg1NzY2MjExNzY2NzMwNjk3X24uanBnP29oPTE0OGQyZWVlNjRiYjE0YWZjZDg5MWIyZDVjMWQ2Zjg2Jm9lPTU5MkYzRUJDIiwic3ViIjoiMDliN2ZlYTUtMmU0ZS00MGI4LTlkODEtZGY1MDA3MWEzMDUzIiwiaWRlbnRpdGllcyI6W3sicHJvdmlkZXIiOiJmYWNlYm9vayIsImlkIjoiMzc3NDQwMTU5Mjc1NjU5In1dLCJhbXIiOlsiZmFjZWJvb2siXSwib2F1dGhfY2xpZW50Ijp7Im5hbWUiOiJhcHBpZCIsInR5cGUiOiJtb2JpbGVhcHAiLCJzb2Z0d2FyZV9pZCI6ImNvbS5pYm0ubW9iaWxlZmlyc3RwbGF0Zm9ybS5hcHBpZCIsInNvZnR3YXJlX3ZlcnNpb24iOiIxLjAiLCJkZXZpY2VfaWQiOiJlZWUyYzc4ZC0wZjEyLTM4MDgtOTFlYi1jNjM0NzVkYmJmOTUiLCJkZXZpY2VfbW9kZWwiOiJHVC1JOTUwMCIsImRldmljZV9vcyI6ImFuZHJvaWQifX0.Iy0l7C5mT8vum46G8Depk4KRXmOyBlJSWTRoPP41cXztSAqwOEZOXo4IWJVnwia46UbRgJ751VYZId2KTGap8H8R1sT-DkB8o27k8aIUT8dp0oNdnnjBYZR5sI5FLaqGJ02g8oddTlx2Dhb_XxZ4GwtfDCXLvIPgi3Q-1GrPjWNWOMP279KuBpy1a5KfOspQXp69rTaMJFXBzTo2ekVCKhx1mKwLRMWaE4RWkcwtl880lH6Nutz9B0ZneFrFl9MdNYH4y4BpWCUZKDobqgDl7kZFeSg5Zj8knOdlieDgevNKqMXAFERnV6q5pg2xgg5r-uvrjl7dg4Hol7j_MyTp8w"; + public static final String REFRESH_TOKEN = "ENCODEDREFRESHTOKEN"; } diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/testing/helpers/ExceptionMessageMatcher.java b/lib/src/test/java/com/ibm/bluemix/appid/android/testing/helpers/ExceptionMessageMatcher.java new file mode 100644 index 0000000..0a68c0b --- /dev/null +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/testing/helpers/ExceptionMessageMatcher.java @@ -0,0 +1,16 @@ +package com.ibm.bluemix.appid.android.testing.helpers; + +import org.mockito.ArgumentMatcher; + +public class ExceptionMessageMatcher extends ArgumentMatcher { + private String message; + + public ExceptionMessageMatcher(String message) { + this.message = message; + } + + @Override + public boolean matches(Object argument) { + return ((Exception)argument).getMessage().equals(message); + } +} diff --git a/lib/src/test/java/com/ibm/bluemix/appid/android/testing/mocks/Response_Mock.java b/lib/src/test/java/com/ibm/bluemix/appid/android/testing/mocks/Response_Mock.java new file mode 100644 index 0000000..1453e43 --- /dev/null +++ b/lib/src/test/java/com/ibm/bluemix/appid/android/testing/mocks/Response_Mock.java @@ -0,0 +1,64 @@ +/* + Copyright 2017 IBM Corp. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package com.ibm.bluemix.appid.android.testing.mocks; + +import com.ibm.mobilefirstplatform.clientsdk.android.core.api.Response; + +import org.json.JSONObject; + +import java.io.InputStream; +import java.util.List; +import java.util.Map; + +public class Response_Mock implements Response { + private int statusCode; + private String responseText; + + public Response_Mock(String resposneText, int statusCode) { + this.responseText = resposneText; + this.statusCode = statusCode; + } + + public String getRequestURL() { + return null; + } + + public int getStatus() { + return statusCode; + } + + public String getResponseText() { + return responseText; + } + + public JSONObject getResponseJSON() { + return null; + } + + public byte[] getResponseBytes() { + return new byte[0]; + } + + public InputStream getResponseByteStream() { + return null; + } + + public long getContentLength() { + return 0; + } + + public Map> getHeaders() { + return null; + } +}