From b235bbf42393dcf273e4d777631d841f0ebf73ea Mon Sep 17 00:00:00 2001 From: Stefan Schueller Date: Sun, 5 Jul 2020 20:51:53 +0200 Subject: [PATCH] Added token refresh. --- .../network/AccessTokenAuthenticator.java | 49 ++++++++++++ .../network/AuthenticationService.java | 10 +++ .../network/AuthorizationInterceptor.java | 5 +- .../peertube/network/RetrofitInstance.java | 1 + .../schueller/peertube/network/Session.java | 39 +++------- .../peertube/service/LoginService.java | 76 +++++++++++++++++-- app/src/main/res/values/constants.xml | 3 + 7 files changed, 147 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/net/schueller/peertube/network/AccessTokenAuthenticator.java diff --git a/app/src/main/java/net/schueller/peertube/network/AccessTokenAuthenticator.java b/app/src/main/java/net/schueller/peertube/network/AccessTokenAuthenticator.java new file mode 100644 index 0000000..f877efc --- /dev/null +++ b/app/src/main/java/net/schueller/peertube/network/AccessTokenAuthenticator.java @@ -0,0 +1,49 @@ +package net.schueller.peertube.network; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import okhttp3.Authenticator; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.Route; + +public class AccessTokenAuthenticator implements Authenticator { + + public AccessTokenAuthenticator() { + } + + @Nullable + @Override + public Request authenticate(Route route, @NonNull Response response) { + Session session = Session.getInstance(); + + final String accessToken = session.getToken(); + if (!isRequestWithAccessToken(response) || accessToken == null) { + return null; + } + synchronized (this) { + final String newAccessToken = session.getToken(); + // Access token is refreshed in another thread. + if (!accessToken.equals(newAccessToken)) { + return newRequestWithAccessToken(response.request(), newAccessToken); + } + + // Need to refresh an access token + final String updatedAccessToken = session.refreshAccessToken(); + return newRequestWithAccessToken(response.request(), updatedAccessToken); + } + } + + private boolean isRequestWithAccessToken(@NonNull Response response) { + String header = response.request().header("Authorization"); + return header != null && header.startsWith("Bearer"); + } + + @NonNull + private Request newRequestWithAccessToken(@NonNull Request request, @NonNull String accessToken) { + return request.newBuilder() + .header("Authorization", accessToken) + .build(); + } +} \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/network/AuthenticationService.java b/app/src/main/java/net/schueller/peertube/network/AuthenticationService.java index 898c9bd..9c1393d 100644 --- a/app/src/main/java/net/schueller/peertube/network/AuthenticationService.java +++ b/app/src/main/java/net/schueller/peertube/network/AuthenticationService.java @@ -42,4 +42,14 @@ public interface AuthenticationService { @Field("password") String password ); + @POST("users/token") + @FormUrlEncoded + Call refreshToken( + @Field("client_id") String clientId, + @Field("client_secret") String clientSecret, + @Field("grant_type") String grantType, + @Field("response_type") String responseType, + @Field("username") String username, + @Field("refresh_token") String refreshToken + ); } \ No newline at end of file diff --git a/app/src/main/java/net/schueller/peertube/network/AuthorizationInterceptor.java b/app/src/main/java/net/schueller/peertube/network/AuthorizationInterceptor.java index 13b2925..fdde217 100644 --- a/app/src/main/java/net/schueller/peertube/network/AuthorizationInterceptor.java +++ b/app/src/main/java/net/schueller/peertube/network/AuthorizationInterceptor.java @@ -20,6 +20,8 @@ package net.schueller.peertube.network; import android.util.Log; +import androidx.annotation.NonNull; + import java.io.IOException; import okhttp3.Interceptor; @@ -31,8 +33,7 @@ public class AuthorizationInterceptor implements Interceptor { public AuthorizationInterceptor() { } - - + @NonNull @Override public Response intercept(Chain chain) throws IOException { diff --git a/app/src/main/java/net/schueller/peertube/network/RetrofitInstance.java b/app/src/main/java/net/schueller/peertube/network/RetrofitInstance.java index 23e38da..740f0ae 100644 --- a/app/src/main/java/net/schueller/peertube/network/RetrofitInstance.java +++ b/app/src/main/java/net/schueller/peertube/network/RetrofitInstance.java @@ -33,6 +33,7 @@ public class RetrofitInstance { OkHttpClient.Builder okhttpClientBuilder = new OkHttpClient.Builder(); okhttpClientBuilder.addInterceptor(new AuthorizationInterceptor()); + okhttpClientBuilder.authenticator(new AccessTokenAuthenticator()); retrofit = new retrofit2.Retrofit.Builder() .client(okhttpClientBuilder.build()) diff --git a/app/src/main/java/net/schueller/peertube/network/Session.java b/app/src/main/java/net/schueller/peertube/network/Session.java index 525cbb9..015ef31 100644 --- a/app/src/main/java/net/schueller/peertube/network/Session.java +++ b/app/src/main/java/net/schueller/peertube/network/Session.java @@ -20,24 +20,25 @@ package net.schueller.peertube.network; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import android.util.Log; import net.schueller.peertube.R; import net.schueller.peertube.application.AppApplication; +import static net.schueller.peertube.service.LoginService.refreshToken; + public class Session { private static volatile Session sSoleInstance; private static SharedPreferences sharedPreferences; //private constructor. - private Session(){ + private Session() { Context context = AppApplication.getContext(); sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); //Prevent form the reflection api. - if (sSoleInstance != null){ + if (sSoleInstance != null) { throw new RuntimeException("Use getInstance() method to get the single instance of this class."); } } @@ -58,7 +59,6 @@ public class Session { } - public boolean isLoggedIn() { // check if token exist or not // return true if exist otherwise false @@ -69,13 +69,6 @@ public class Session { return getToken() != null; } - public void saveToken(String token) { - // save the token - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(AppApplication.getContext().getString(R.string.pref_token_access), token); - editor.commit(); - } - public String getToken() { // return the token that was saved earlier @@ -89,27 +82,19 @@ public class Session { return null; } - public void saveUsername(String username) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(AppApplication.getContext().getString(R.string.pref_auth_username), username); - editor.commit(); - } - - public String getEmail() { - return sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_auth_username), null); - } - - public void savePassword(String password) { - SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(AppApplication.getContext().getString(R.string.pref_auth_password), password); - editor.commit(); - } - public String getPassword() { return sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_auth_password), null); } + public String refreshAccessToken() { + + refreshToken(); + // refresh token + + return this.getToken(); + } + public void invalidate() { // get called when user become logged out // delete token and other user info diff --git a/app/src/main/java/net/schueller/peertube/service/LoginService.java b/app/src/main/java/net/schueller/peertube/service/LoginService.java index 6891c92..628d5be 100644 --- a/app/src/main/java/net/schueller/peertube/service/LoginService.java +++ b/app/src/main/java/net/schueller/peertube/service/LoginService.java @@ -26,6 +26,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import net.schueller.peertube.R; +import net.schueller.peertube.application.AppApplication; import net.schueller.peertube.helper.APIUrlHelper; import net.schueller.peertube.model.OauthClient; import net.schueller.peertube.model.Token; @@ -42,8 +43,7 @@ public class LoginService { private static final String TAG = "LoginService"; - public static void Authenticate(String username, String password) - { + public static void Authenticate(String username, String password) { Context context = getContext(); String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); @@ -62,6 +62,14 @@ public class LoginService { OauthClient oauthClient = response.body(); + SharedPreferences.Editor editor = sharedPref.edit(); + + assert oauthClient != null; + + editor.putString(context.getString(R.string.pref_client_id), oauthClient.getClientId()); + editor.putString(context.getString(R.string.pref_client_secret), oauthClient.getClientSecret()); + editor.apply(); + Call call2 = service.getAuthenticationToken( oauthClient.getClientId(), oauthClient.getClientSecret(), @@ -79,11 +87,7 @@ public class LoginService { Token token = response2.body(); - SharedPreferences.Editor editor = sharedPref.edit(); - - // TODO: calc expiration - //editor.putInt(getString(R.string.pref_token_expiration), token.getRefreshToken()); - + assert token != null; editor.putString(context.getString(R.string.pref_token_access), token.getAccessToken()); editor.putString(context.getString(R.string.pref_token_refresh), token.getExpiresIn()); editor.putString(context.getString(R.string.pref_token_type), token.getTokenType()); @@ -124,4 +128,62 @@ public class LoginService { } }); } + + public static void refreshToken() { + Context context = getContext(); + + String apiBaseURL = APIUrlHelper.getUrlWithVersion(context); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + AuthenticationService service = RetrofitInstance.getRetrofitInstance(apiBaseURL).create(AuthenticationService.class); + + String refreshToken = sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_token_refresh), null); + String userName = sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_auth_username), null); + String clientId = sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_client_id), null); + String clientSecret = sharedPreferences.getString(AppApplication.getContext().getString(R.string.pref_client_secret), null); + + + Call call = service.refreshToken( + clientId, + clientSecret, + "refresh_token", + "code", + userName, + refreshToken + ); + call.enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull retrofit2.Response response) { + + if (response.isSuccessful()) { + + Token token = response.body(); + + SharedPreferences.Editor editor = sharedPreferences.edit(); + + assert token != null; + editor.putString(context.getString(R.string.pref_token_access), token.getAccessToken()); + editor.putString(context.getString(R.string.pref_token_refresh), token.getExpiresIn()); + editor.putString(context.getString(R.string.pref_token_type), token.getTokenType()); + editor.apply(); + + Log.wtf(TAG, "Logged in"); + + Toast.makeText(context, context.getString(R.string.authentication_login_success), Toast.LENGTH_LONG).show(); + + + } else { + Log.wtf(TAG, response.toString()); + Toast.makeText(context, context.getString(R.string.authentication_login_failed), Toast.LENGTH_LONG).show(); + + } + } + + @Override + public void onFailure(@NonNull Call call2, @NonNull Throwable t2) { + Log.wtf("err", t2.fillInStackTrace()); + Toast.makeText(context, context.getString(R.string.authentication_login_failed), Toast.LENGTH_LONG).show(); + + } + }); + } } diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml index 1adf716..7c0916c 100644 --- a/app/src/main/res/values/constants.xml +++ b/app/src/main/res/values/constants.xml @@ -26,6 +26,8 @@ pref_token_type pref_auth_username pref_auth_password + pref_client_id + pref_client_secret pref_api_base pref_quality @@ -34,6 +36,7 @@ backgroundStop backgroundFloat + 1.0.0-alpha.7 BACKGROUND_AUDIO