public class Endpoints { public static final String OAUTH2_BASE_URL = "https://login.microsoftonline.com"; public static final String OAUTH2_ENDPOINT = "/oauth2"; public static final String OAUTH2_AUTHORIZATION_ENDPOINT = "/authorize"; public static final String OAUTH2_TOKEN_ENDPOINT = "/token"; public static final String OAUTH2_TENANT_PATH_FIELD = "/{tenant}"; }
public class QueryFields { public static final String QUERY_OAUTH2_CLIENT_ID = "client_id"; public static final String QUERY_OAUTH2_RESPONSE_TYPE = "response_type"; public static final String QUERY_OAUTH2_REDIRECT_URI = "redirect_uri"; public static final String QUERY_OAUTH2_RESOURCE = "resource"; }
public class RequestFields { public static final String OAUTH2_CLIENT_ID = "client_id"; public static final String OAUTH2_GRANT_TYPE = "grant_type"; public static final String OAUTH2_RESOURCE = "resource"; public static final String OAUTH2_CODE = "code"; public static final String OAUTH2_REDIRECT_URI = "redirect_uri"; public static final String OAUTH2_RAW_CODE_QUERY_FIELD = "?code"; public static final String OAUTH2_CODE_QUERY_FIELD = "code"; public static final String OAUTH2_RAW_QEURY_ERROR_FIELD = "error="; }
public class RequestFieldValues { public static final String TENANT_COMMON = "common"; public static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token"; }
public class ResponseFields { public static final String OAUTH2_TOKEN_TYPE = "token_type"; public static final String OAUTH2_TOKEN_EXPIRES_IN = "expires_in"; public static final String OAUTH2_TOKEN_SCOPE = "scope"; public static final String OAUTH2_TOKEN_EXPIRES_ON = "expires_on"; public static final String OAUTH2_TOKEN_NOT_BEFORE = "not_before"; public static final String OAUTH2_TOKEN_RESOURCE = "resource"; public static final String OAUTH2_TOKEN_ACCESS_TOKEN = "access_token"; public static final String OAUTH2_TOKEN_REFRESH_TOKEN = "refresh_token"; public static final String OAUTH2_TOKEN_ID_TOKEN = "id_token"; }
public class Const { public static int CONNECT_TIMEOUT = 15; public static int WRITE_TIMEOUT = 60; public static int TIMEOUT = 60; }
OAuth2
interface, which contains the request signatures and the API factory, and OAuth2WebViewClient
, which is the customized WebViewClient. @FormUrlEncoded @POST(OAUTH2_TENANT_PATH_FIELD + OAUTH2_ENDPOINT + OAUTH2_TOKEN_ENDPOINT) Observable<Response<Token>> tradeCodeForToken( @Path(OAUTH2_TENANT_PATH_FIELD) String tenant, @Field(OAUTH2_CLIENT_ID) String clientId, @Field(OAUTH2_GRANT_TYPE) String grantType, @Field(OAUTH2_RESOURCE) String resource, @Field(OAUTH2_CODE) String code, @Field(OAUTH2_REDIRECT_URI) String redirectUri );
@FormUrlEncoded @POST(OAUTH2_TENANT_PATH_FIELD + OAUTH2_ENDPOINT + OAUTH2_TOKEN_ENDPOINT) Observable<Response<Token>> refreshToken( @Path(OAUTH2_TENANT_PATH_FIELD) String tenant, @Field(OAUTH2_CLIENT_ID) String clientId, @Field(OAUTH2_GRANT_TYPE) String grantType, @Field(OAUTH2_RESOURCE) String resource, @Field(OAUTH2_TOKEN_REFRESH_TOKEN) String refreshToken, @Field(OAUTH2_REDIRECT_URI) String redirectUri );
class Factory { public static OAuth2 buildOAuth2API(boolean enableDebug) { return buildRetrofit(OAUTH2_BASE_URL, enableDebug).create(OAuth2.class); } protected static Retrofit buildRetrofit(String baseUrl, boolean enableDebug) { return new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create())) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(buildClient(enableDebug)) .build(); } protected static OkHttpClient buildClient(boolean enableDebug) { OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectTimeout(Const.CONNECT_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(Const.WRITE_TIMEOUT, TimeUnit.SECONDS) .readTimeout(Const.TIMEOUT, TimeUnit.SECONDS); if(enableDebug) { builder.addInterceptor( new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY) ); } return builder.build(); } }
public interface OAuth2 { /** The request signature that returns a deserialized token */ @FormUrlEncoded @POST(OAUTH2_TENANT_PATH_FIELD + OAUTH2_ENDPOINT + OAUTH2_TOKEN_ENDPOINT) Observable<Response<Token>> tradeCodeForToken( @Path(OAUTH2_TENANT_PATH_FIELD) String tenant, @Field(OAUTH2_CLIENT_ID) String clientId, @Field(OAUTH2_GRANT_TYPE) String grantType, @Field(OAUTH2_RESOURCE) String resource, @Field(OAUTH2_CODE) String code, @Field(OAUTH2_REDIRECT_URI) String redirectUri ); /** The request signature that returns a raw json object instead of deserealized token */ @FormUrlEncoded @POST(OAUTH2_TENANT_PATH_FIELD + OAUTH2_ENDPOINT + OAUTH2_TOKEN_ENDPOINT) Observable<Response<JsonObject>> tradeCodeForTokenRaw( @Path(OAUTH2_TENANT_PATH_FIELD) String tenant, @Field(OAUTH2_CLIENT_ID) String clientId, @Field(OAUTH2_GRANT_TYPE) String grantType, @Field(OAUTH2_RESOURCE) String resource, @Field(OAUTH2_CODE) String code, @Field(OAUTH2_REDIRECT_URI) String redirectUri ); /** The request signature that allows refreshing token */ @FormUrlEncoded @POST(OAUTH2_TENANT_PATH_FIELD + OAUTH2_ENDPOINT + OAUTH2_TOKEN_ENDPOINT) Observable<Response<Token>> refreshToken( @Path(OAUTH2_TENANT_PATH_FIELD) String tenant, @Field(OAUTH2_CLIENT_ID) String clientId, @Field(OAUTH2_GRANT_TYPE) String grantType, @Field(OAUTH2_RESOURCE) String resource, @Field(OAUTH2_TOKEN_REFRESH_TOKEN) String refreshToken, @Field(OAUTH2_REDIRECT_URI) String redirectUri ); /** The request signature that allows refreshing token and returns a raw json instead of deserialized token */ @FormUrlEncoded @POST(OAUTH2_TENANT_PATH_FIELD + OAUTH2_ENDPOINT + OAUTH2_TOKEN_ENDPOINT) Observable<Response<Token>> refreshTokenRaw( @Path(OAUTH2_TENANT_PATH_FIELD) String tenant, @Field(OAUTH2_CLIENT_ID) String clientId, @Field(OAUTH2_GRANT_TYPE) String grantType, @Field(OAUTH2_RESOURCE) String resource, @Field(OAUTH2_TOKEN_REFRESH_TOKEN) String refreshToken, @Field(OAUTH2_REDIRECT_URI) String redirectUri ); class Factory { public static OAuth2 buildOAuth2API(boolean enableDebug) { return buildRetrofit(OAUTH2_BASE_URL, enableDebug).create(OAuth2.class); } protected static Retrofit buildRetrofit(String baseUrl, boolean enableDebug) { return new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().create())) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(buildClient(enableDebug)) .build(); } protected static OkHttpClient buildClient(boolean enableDebug) { OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectTimeout(Const.CONNECT_TIMEOUT, TimeUnit.SECONDS) .writeTimeout(Const.WRITE_TIMEOUT, TimeUnit.SECONDS) .readTimeout(Const.TIMEOUT, TimeUnit.SECONDS); if(enableDebug) { builder.addInterceptor( new HttpLoggingInterceptor().setLevel( HttpLoggingInterceptor.Level.BODY ) ); } return builder.build(); } } }
public class Token { @SerializedName(OAUTH2_TOKEN_TYPE) private String tokenType; @SerializedName(OAUTH2_TOKEN_EXPIRES_IN) private String expiresIn; @SerializedName(OAUTH2_TOKEN_SCOPE) private String scope; @SerializedName(OAUTH2_TOKEN_EXPIRES_ON) private String expiresOn; @SerializedName(OAUTH2_TOKEN_NOT_BEFORE) private String notBefore; @SerializedName(OAUTH2_TOKEN_RESOURCE) private String resource; @SerializedName(OAUTH2_TOKEN_ACCESS_TOKEN) private String accessToken; @SerializedName(OAUTH2_TOKEN_REFRESH_TOKEN) private String refreshToken; @SerializedName(OAUTH2_TOKEN_ID_TOKEN) private String idToken; public Token(String tokenType, String expiresIn, String scope, String expiresOn, String notBefore, String resource, String accessToken, String refreshToken, String idToken) { this.tokenType = tokenType; this.expiresIn = expiresIn; this.scope = scope; this.expiresOn = expiresOn; this.notBefore = notBefore; this.resource = resource; this.accessToken = accessToken; this.refreshToken = refreshToken; this.idToken = idToken; } public String getTokenType() { return tokenType; } public void setTokenType(String tokenType) { this.tokenType = tokenType; } public String getExpiresIn() { return expiresIn; } public void setExpiresIn(String expiresIn) { this.expiresIn = expiresIn; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } public String getExpiresOn() { return expiresOn; } public void setExpiresOn(String expiresOn) { this.expiresOn = expiresOn; } public String getNotBefore() { return notBefore; } public void setNotBefore(String notBefore) { this.notBefore = notBefore; } public String getResource() { return resource; } public void setResource(String resource) { this.resource = resource; } public String getAccessToken() { return accessToken; } public void setAccessToken(String accessToken) { this.accessToken = accessToken; } public String getRefreshToken() { return refreshToken; } public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } public String getIdToken() { return idToken; } public void setIdToken(String idToken) { this.idToken = idToken; } @Override public String toString() { return "MicrosoftAzureOAuthToken{" + "tokenType='" + tokenType + '\'' + ", expiresIn='" + expiresIn + '\'' + ", scope='" + scope + '\'' + ", expiresOn='" + expiresOn + '\'' + ", notBefore='" + notBefore + '\'' + ", resource='" + resource + '\'' + ", accessToken='" + accessToken + '\'' + ", refreshToken='" + refreshToken + '\'' + ", idToken='" + idToken + '\'' + '}'; } public String toJsonString() { return new Gson().toJson(this, Token.class); } public static Token fromJsonString(String jsonString) { return new Gson().fromJson(jsonString, Token.class); } }
WebViewClient
methods: shouldOverrideUrlLoading(WebView webView, String url)
and onPageFinished(WebView webView, String url)
. public class OAuth2WebViewClient extends WebViewClient { private Action1<String> onSuccess; private Action1<String> onError; private Action1<String> onUnknownUrlPassed; public OAuth2WebViewClient(Action1<String> onSuccess, Action1<String> onError, Action1<String> onUnknownUrlPassed) { this.onSuccess = onSuccess; this.onUnknownUrlPassed = onUnknownUrlPassed; this.onError = onError; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if(url.contains(OAUTH2_RAW_CODE_QUERY_FIELD) || url.contains(OAUTH2_RAW_QEURY_ERROR_FIELD)) { return true; } else { view.loadUrl(url); return false; } } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if(url.contains(OAUTH2_RAW_CODE_QUERY_FIELD)) { Uri uri = Uri.parse(url); onSuccess.call(uri.getQueryParameter(OAUTH2_CODE_QUERY_FIELD)); } else if(url.contains(OAUTH2_RAW_CODE_QUERY_FIELD)) { onError.call(url); } else { onUnknownUrlPassed.call(url); } } }
public class AzureAuthenticationWebView extends WebView { public AzureAuthenticationWebView(Context context) { super(context); } public AzureAuthenticationWebView(Context context, AttributeSet attrs) { super(context, attrs); } public AzureAuthenticationWebView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public AzureAuthenticationWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public void init(OAuth2WebViewClient client, String query) { WebSettings settings = this.getSettings(); settings.setJavaScriptEnabled(true); settings.setSupportMultipleWindows(true); this.setWebViewClient(client); this.loadUrl(query); } }
public class AzureStorageManager { private ObscuredSharedPreferences preferences; public AzureStorageManager(ObscuredSharedPreferences preferences) { this.preferences = preferences; } public Token readToken() { String rawToken = preferences.getString(TOKEN_JSON_KEY, ""); return Token.fromJsonString(rawToken); } public void writeToken(Token token) { ObscuredSharedPreferences.Editor editor = preferences.edit(); editor.putString(TOKEN_JSON_KEY, token.toJsonString()); editor.commit(); } }
public class QueryStringBuilder { private String query; public QueryStringBuilder(String tenant) { query = OAUTH2_BASE_URL.concat("/").concat(tenant).concat(OAUTH2_ENDPOINT).concat(OAUTH2_AUTHORIZATION_ENDPOINT).concat("?"); } public QueryStringBuilder setClientId(String clientId) { query = prepareQuery(query); query = query.concat(QUERY_OAUTH2_CLIENT_ID).concat("=").concat(clientId); return this; } public QueryStringBuilder setResponseType(String responseType) { query = prepareQuery(query); query = query.concat(QUERY_OAUTH2_RESPONSE_TYPE).concat("=").concat(responseType); return this; } public QueryStringBuilder setRedirectUri(String redirectUri) { query = prepareQuery(query); query = query.concat(QUERY_OAUTH2_REDIRECT_URI).concat("=").concat(redirectUri); return this; } public QueryStringBuilder setResource(String resource) { query = prepareQuery(query); query = query.concat(QUERY_OAUTH2_RESOURCE).concat("=").concat(resource); return this; } public String build() { return query; } private String prepareQuery(String query) { if(query != null && query.length() != 0 && !(String.valueOf(query.charAt(query.length() - 1)).equals("?"))) { query = query.concat("&"); } return query; } }
public class TokenManager { private Subscription subscription = Subscriptions.empty(); private AzureStorageManager storageManager; private String tenantType; private String clientId; private String redirectUri; public TokenManager(AzureStorageManager storageManager, String tenantType, String clientId, String redirectUri) { this.storageManager = storageManager; this.tenantType = tenantType; this.clientId = clientId; this.redirectUri = redirectUri; } /** Performs (code -> token) exchange using MS OAuth2 API * Caches the token if the response code is equals to HTTP_OK */ public void tradeCodeForToken(String code, String resource, final Action1<Token> onSuccess, Action1<Integer> onHttpError, Action1<Throwable> onFailure) { subscription = OAuth2.Factory.buildOAuth2API(false) .tradeCodeForToken( tenantType, clientId, GRANT_TYPE_REFRESH_TOKEN, resource, code, redirectUri ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .filter(response -> { if(response.code() != HTTP_OK) { onHttpError.call(response.code()); return false; } return true; }) .map(Response::body) .subscribe( token -> { storageManager.writeToken(token); onSuccess.call(token); }, e -> { onFailure.call(e); subscription.unsubscribe(); }, () -> subscription.unsubscribe() ); } /** Refreshes expired token * Caches the token if the response code is equals to HTTP_OK */ public void refreshToken(Token expiredToken, final Action1<Token> onSuccess, Action1<Integer> onHttpError, Action1<Throwable> onFailure) { subscription = OAuth2.Factory.buildOAuth2API(false) .refreshToken( tenantType, clientId, GRANT_TYPE_REFRESH_TOKEN, expiredToken.getResource(), expiredToken.getRefreshToken(), redirectUri ) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .filter(response -> { if(response.code() != HTTP_OK) { onHttpError.call(response.code()); return false; } return true; }) .map(Response::body) .subscribe( token -> { storageManager.writeToken(token); onSuccess.call(token); }, e -> { onFailure.call(e); subscription.unsubscribe(); }, () -> subscription.unsubscribe() ); } }
Source: https://habr.com/ru/post/313454/
All Articles