From b235bbf42393dcf273e4d777631d841f0ebf73ea Mon Sep 17 00:00:00 2001 From: Stefan Schueller Date: Sun, 5 Jul 2020 20:51:53 +0200 Subject: [PATCH 1/4] 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 From f1ef75a1dd4fad1dcd5100ae252fbb5e8d30e177 Mon Sep 17 00:00:00 2001 From: Stefan Schueller Date: Sun, 5 Jul 2020 20:54:20 +0200 Subject: [PATCH 2/4] Version Bump --- CHANGELOG.md | 4 ++++ app/build.gradle | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2fe13a..a58607a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### Version 1.0.45 Tag: v1.0.45 (2020-07-??) + * Added token refresh + + ### Version 1.0.44 Tag: v1.0.44 (2020-07-05) * Completed implementation of Likes & Dislikes (@Poslovitch) * Added preview of the current playback speed and video quality in the VideoOptionsFragment (@Poslovitch) diff --git a/app/build.gradle b/app/build.gradle index 4f789c7..ce3bd4e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ android { applicationId "net.schueller.peertube" minSdkVersion 21 targetSdkVersion 29 - versionCode 1044 - versionName "1.0.44" + versionCode 1045 + versionName "1.0.45" //buildTime readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' //buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' //resValue "string", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' From d594bb56857c60a85d07aaa38e561b75ff3fa7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Schu=CC=88ller?= Date: Mon, 6 Jul 2020 21:48:26 +0200 Subject: [PATCH 3/4] Reproducible Build --- Dockerfile | 4 ++- REPRODUCIBLE_BUILDS.md | 11 +++---- apkdiff.py | 66 ++++++++++++++++++++++++++++++++++++++++++ gradlew | 0 4 files changed, 75 insertions(+), 6 deletions(-) create mode 100755 apkdiff.py mode change 100644 => 100755 gradlew diff --git a/Dockerfile b/Dockerfile index 04ef9f4..99189c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ -FROM gradle:5.6.4-jdk8 +FROM gradle:6.1.1-jdk8 ENV ANDROID_SDK_URL https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip +ENV ANDROID_SDK_CHECKSUM 444e22ce8ca0f67353bda4b85175ed3731cae3ffa695ca18119cbacef1c1bea0 ENV ANDROID_BUILD_TOOLS_VERSION 29.0.3 ENV ANDROID_HOME /usr/local/android-sdk-linux ENV ANDROID_VERSION 29 @@ -9,6 +10,7 @@ ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools RUN mkdir "$ANDROID_HOME" .android && \ cd "$ANDROID_HOME" && \ curl -o sdk.zip $ANDROID_SDK_URL && \ + echo "${ANDROID_SDK_CHECKSUM} sdk.zip" | sha256sum -c - && \ unzip sdk.zip && \ rm sdk.zip diff --git a/REPRODUCIBLE_BUILDS.md b/REPRODUCIBLE_BUILDS.md index 00bf5ef..dc08153 100644 --- a/REPRODUCIBLE_BUILDS.md +++ b/REPRODUCIBLE_BUILDS.md @@ -1,6 +1,6 @@ # Reproducible Builds -Note: reproducible builds work starting version 1.0.44 +Note: reproducible builds work starting version 1.0.45 ## Install Docker @@ -34,8 +34,8 @@ git checkout v1.0.44 ```shell cd ~/peertube-android docker build -t thorium-builder . -docker run --rm -v ~/peertube-android:/home/peertube-android -w /home/peertube-android thorium-builder gradle assembleProdRelease -PkeystorePassword=securePassword -PkeyAliasPassword=securePassword -PkeystoreFile=build.keystore -PbuildTimestamp=1593942384524 -cp app/build/outputs/apk/prod/release/app-prod-release.apk thorium-built.apk +docker run --rm -v ~/Private/peertube:/home/peertube -w /home/peertube thorium-builder gradle assembleRelease -PkeystorePassword=securePassword -PkeyAliasPassword=securePassword -PkeystoreFile=build.keystore -PbuildTimestamp=1593973044091 +cp app/build/outputs/apk/release/app-release-unsigned.apk thorium-built.apk ``` ## Extract the Play Store APK from your phone @@ -46,7 +46,8 @@ cp app/build/outputs/apk/prod/release/app-prod-release.apk thorium-built.apk ```shell cd ~/peertube-android -adb pull `adb shell pm path net.schueller.peertube | cut -d':' -f2` thorium-store.apk +adb shell pm path net.schueller.peertube +adb pull /data/app/net.schueller.peertube-mCeISw_AujlMBHyPfVhdSg==/base.apk thorium-store.apk ``` ## Compare the two files @@ -56,6 +57,6 @@ adb pull `adb shell pm path net.schueller.peertube | cut -d':' -f2` thorium-stor ```shell cd ~/peertube-android -python apkdiff.py thorium-built.apk thorium-store.apk +./apkdiff.py thorium-built.apk thorium-store.apk ``` diff --git a/apkdiff.py b/apkdiff.py new file mode 100755 index 0000000..13df537 --- /dev/null +++ b/apkdiff.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# Taken from https://github.com/DrKLO/Telegram/blob/master/apkdiff.py on June 4th, 2020 + +import sys +from zipfile import ZipFile + +def compareFiles(first, second): + while True: + firstBytes = first.read(4096); + secondBytes = second.read(4096); + if firstBytes != secondBytes: + return False + + if firstBytes == b"": + break + + return True + +def compare(first, second): + FILES_TO_IGNORE = ["META-INF/MANIFEST.MF", "META-INF/CERT.RSA", "META-INF/CERT.SF"] + + firstZip = ZipFile(first, 'r') + secondZip = ZipFile(second, 'r') + + firstList = list(filter(lambda firstInfo: firstInfo.filename not in FILES_TO_IGNORE, firstZip.infolist())) + secondList = list(filter(lambda secondInfo: secondInfo.filename not in FILES_TO_IGNORE, secondZip.infolist())) + + if len(firstList) != len(secondList): + print("APKs has different amount of files (%d != %d)" % (len(firstList), len(secondList))) + return False + + for firstInfo in firstList: + found = False + for secondInfo in secondList: + if firstInfo.filename == secondInfo.filename: + found = True + firstFile = firstZip.open(firstInfo, 'r') + secondFile = secondZip.open(secondInfo, 'r') + + if compareFiles(firstFile, secondFile) != True: + print("APK file %s does not match" % firstInfo.filename) + return False + + secondList.remove(secondInfo) + break + + if found == False: + print("file %s not found in second APK" % firstInfo.filename) + return False + + if len(secondList) != 0: + for secondInfo in secondList: + print("file %s not found in first APK" % secondInfo.filename) + return False + + return True + +if __name__ == '__main__': + if len(sys.argv) != 3: + print("Usage: apkdiff ") + sys.exit(1) + + if sys.argv[1] == sys.argv[2] or compare(sys.argv[1], sys.argv[2]) == True: + print("APKs are the same!") + else: + print("APKs are different!") \ No newline at end of file diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 832e103a11655c5e58fb5f889a9345553e9ec0a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Schu=CC=88ller?= Date: Wed, 8 Jul 2020 12:31:30 +0200 Subject: [PATCH 4/4] Reproducible Builds --- CHANGELOG.md | 3 +-- app/build.gradle | 5 +---- .../schueller/peertube/activity/SettingsActivity.java | 9 +++++++++ app/src/main/res/xml/root_preferences.xml | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a58607a..b474dcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,6 @@ -### Version 1.0.45 Tag: v1.0.45 (2020-07-??) +### Version 1.0.45 Tag: v1.0.45 (2020-07-08) * Added token refresh - ### Version 1.0.44 Tag: v1.0.44 (2020-07-05) * Completed implementation of Likes & Dislikes (@Poslovitch) * Added preview of the current playback speed and video quality in the VideoOptionsFragment (@Poslovitch) diff --git a/app/build.gradle b/app/build.gradle index ce3bd4e..d8b2be1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,9 +25,7 @@ android { targetSdkVersion 29 versionCode 1045 versionName "1.0.45" - //buildTime readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' - //buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' - //resValue "string", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' + buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ext { libVersions = [ @@ -107,7 +105,6 @@ android { applicationVariants.all { variant -> variant.resValue "string", "versionName", variant.versionName - variant.resValue "string", "buildTime", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + '' } } diff --git a/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java b/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java index 6f559b2..c2e0083 100644 --- a/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java +++ b/app/src/main/java/net/schueller/peertube/activity/SettingsActivity.java @@ -18,11 +18,14 @@ package net.schueller.peertube.activity; import android.os.Bundle; +import android.view.View; import androidx.appcompat.app.ActionBar; import androidx.appcompat.widget.Toolbar; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import net.schueller.peertube.BuildConfig; import net.schueller.peertube.R; public class SettingsActivity extends CommonActivity { @@ -59,6 +62,12 @@ public class SettingsActivity extends CommonActivity { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); + + // write Build Time into pref + Preference pref = findPreference("pref_buildtime"); + assert pref != null; + pref.setSummary(Long.toString(BuildConfig.BUILD_TIME)); + } } } \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index bff8b4c..795ebb8 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -85,7 +85,8 @@ app:iconSpaceReserved="false"/>