From 0e14540b69f6c47f081b4c26d527686cbf007fdd Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 31 Dec 2020 11:19:43 +0100 Subject: [PATCH] Allow connection of Mastodon & Pleroma accounts --- app/src/main/AndroidManifest.xml | 17 +- .../fedilab/fedilabtube/LoginActivity.java | 118 +++++------- .../MastodonWebviewConnectActivity.java | 181 ++++++++++++++++++ .../fedilabtube/client/MastodonService.java | 17 +- .../client/RetrofitMastodonAPI.java | 22 +-- .../client/RetrofitPeertubeAPI.java | 2 +- .../client/entities/OauthParams.java | 20 ++ .../fedilab/fedilabtube/helper/Helper.java | 2 + app/src/main/res/values/strings.xml | 1 + 9 files changed, 288 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/app/fedilab/fedilabtube/MastodonWebviewConnectActivity.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 27cd325..a4cad0b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -129,11 +129,26 @@ android:name=".WebviewConnectActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/app_name" /> + + android:windowSoftInputMode="stateAlwaysHidden"> + + + + + + + + + { - boolean connectAPeertubeAccount = true; WellKnownNodeinfo.NodeInfo instanceNodeInfo = null; if (BuildConfig.allow_remote_connections) { instanceNodeInfo = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).getNodeInfo(); - if (instanceNodeInfo != null && instanceNodeInfo.getSoftware().getName().toUpperCase().trim().compareTo("PEERTUBE") != 0) { - connectAPeertubeAccount = false; - } - } - if (connectAPeertubeAccount) { - connectToPeertube(finalInstance); - } else { - connectToFediverse(finalInstance, instanceNodeInfo); } + connectToFediverse(finalInstance, instanceNodeInfo); }).start(); } } }); } - private void connectToFediverse(String finalInstance, WellKnownNodeinfo.NodeInfo instanceNodeInfo) { - switch (instanceNodeInfo.getSoftware().getName().toUpperCase().trim()) { - case "MASTODON": - case "PLEROMA": - Oauth oauth = new RetrofitMastodonAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.REDIRECT_CONTENT, Helper.OAUTH_SCOPES_MASTODON, Helper.WEBSITE_VALUE); - if (oauth == null) { - runOnUiThread(() -> { - binding.loginButton.setEnabled(true); - Toasty.error(LoginActivity.this, getString(R.string.client_error), Toast.LENGTH_LONG).show(); - }); - return; - } - client_id = oauth.getClient_id(); - client_secret = oauth.getClient_secret(); - - SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.CLIENT_ID, client_id); - editor.putString(Helper.CLIENT_SECRET, client_secret); - editor.apply(); - OauthParams oauthParams = new OauthParams(); - oauthParams.setClient_id(client_id); - oauthParams.setClient_secret(client_secret); - oauthParams.setGrant_type("password"); - oauthParams.setScope("user"); - if (binding.loginUid.getText() != null) { - oauthParams.setUsername(binding.loginUid.getText().toString().trim()); - } - if (binding.loginPasswd.getText() != null) { - oauthParams.setPassword(binding.loginPasswd.getText().toString()); - } - try { - Token token = new RetrofitMastodonAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams); - proceedLogin(token, finalInstance, instanceNodeInfo.getSoftware().getName().toUpperCase().trim()); - } catch (final Exception | Error e) { - oauthParams.setUsername(binding.loginUid.getText().toString().toLowerCase().trim()); - try { - Token token = new RetrofitMastodonAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams); - proceedLogin(token, finalInstance, instanceNodeInfo.getSoftware().getName().toUpperCase().trim()); - } catch (Error error) { - Error.displayError(LoginActivity.this, error); - error.printStackTrace(); - runOnUiThread(() -> binding.loginButton.setEnabled(true)); - } - } - break; - - case "FRIENDICA": - - break; - } - - } - /** * Oauth process for Peertube * * @param finalInstance String */ - private void connectToPeertube(String finalInstance) { - Oauth oauth = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.WEBSITE_VALUE, Helper.OAUTH_SCOPES_PEERTUBE, Helper.WEBSITE_VALUE); + private void connectToFediverse(String finalInstance, WellKnownNodeinfo.NodeInfo instanceNodeInfo) { + Oauth oauth = null; + String software; + if (instanceNodeInfo != null) { + software = instanceNodeInfo.getSoftware().getName().toUpperCase().trim(); + switch (software) { + case "MASTODON": + case "PLEROMA": + oauth = new RetrofitMastodonAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.REDIRECT_CONTENT_WEB, Helper.OAUTH_SCOPES_MASTODON, Helper.WEBSITE_VALUE); + break; + + case "FRIENDICA": + + break; + + default: + oauth = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.WEBSITE_VALUE, Helper.OAUTH_SCOPES_PEERTUBE, Helper.WEBSITE_VALUE); + } + } else { + oauth = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).oauthClient(Helper.CLIENT_NAME_VALUE, Helper.WEBSITE_VALUE, Helper.OAUTH_SCOPES_PEERTUBE, Helper.WEBSITE_VALUE); + software = "PEERTUBE"; + } if (oauth == null) { runOnUiThread(() -> { binding.loginButton.setEnabled(true); @@ -344,7 +302,6 @@ public class LoginActivity extends AppCompatActivity { }); return; } - client_id = oauth.getClient_id(); client_secret = oauth.getClient_secret(); @@ -357,7 +314,12 @@ public class LoginActivity extends AppCompatActivity { oauthParams.setClient_id(client_id); oauthParams.setClient_secret(client_secret); oauthParams.setGrant_type("password"); - oauthParams.setScope("user"); + final boolean isMastodonAPI = software.compareTo("MASTODON") == 0 || software.compareTo("PLEROMA") == 0; + if (software.compareTo("PEERTUBE") == 0) { + oauthParams.setScope("user"); + } else if (isMastodonAPI) { + oauthParams.setScope("read write follow"); + } if (binding.loginUid.getText() != null) { oauthParams.setUsername(binding.loginUid.getText().toString().trim()); } @@ -365,13 +327,26 @@ public class LoginActivity extends AppCompatActivity { oauthParams.setPassword(binding.loginPasswd.getText().toString()); } try { - Token token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams); - proceedLogin(token, finalInstance, null); + Token token = null; + if (software.compareTo("PEERTUBE") == 0) { + token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams); + } else if (isMastodonAPI) { + Intent i = new Intent(LoginActivity.this, MastodonWebviewConnectActivity.class); + i.putExtra("software", software); + i.putExtra("instance", finalInstance); + i.putExtra("client_id", client_id); + i.putExtra("client_secret", client_secret); + startActivity(i); + return; + } + proceedLogin(token, finalInstance, software.compareTo("PEERTUBE") == 0 ? null : software); } catch (final Exception | Error e) { oauthParams.setUsername(binding.loginUid.getText().toString().toLowerCase().trim()); try { - Token token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams); - proceedLogin(token, finalInstance, null); + if (software.compareTo("PEERTUBE") == 0) { + Token token = new RetrofitPeertubeAPI(LoginActivity.this, finalInstance, null).manageToken(oauthParams); + proceedLogin(token, finalInstance, software.compareTo("PEERTUBE") == 0 ? null : software); + } } catch (Error error) { Error.displayError(LoginActivity.this, error); error.printStackTrace(); @@ -380,6 +355,7 @@ public class LoginActivity extends AppCompatActivity { } } + @SuppressLint("ApplySharedPref") private void proceedLogin(Token token, String host, String software) { runOnUiThread(() -> { diff --git a/app/src/main/java/app/fedilab/fedilabtube/MastodonWebviewConnectActivity.java b/app/src/main/java/app/fedilab/fedilabtube/MastodonWebviewConnectActivity.java new file mode 100644 index 0000000..0176f58 --- /dev/null +++ b/app/src/main/java/app/fedilab/fedilabtube/MastodonWebviewConnectActivity.java @@ -0,0 +1,181 @@ +/* Copyright 2020 Thomas Schneider + * + * This file is a part of TubeLab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * TubeLab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with TubeLab; if not, + * see . */ + +package app.fedilab.fedilabtube; + + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ProgressBar; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; + +import app.fedilab.fedilabtube.client.RetrofitMastodonAPI; +import app.fedilab.fedilabtube.client.entities.Error; +import app.fedilab.fedilabtube.client.entities.OauthParams; +import app.fedilab.fedilabtube.client.entities.Token; +import app.fedilab.fedilabtube.helper.Helper; +import es.dmoral.toasty.Toasty; + + +public class MastodonWebviewConnectActivity extends AppCompatActivity { + + + private WebView webView; + private AlertDialog alert; + private String clientId, clientSecret; + private String instance, software; + + @SuppressWarnings("deprecation") + public static void clearCookies(Context context) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + CookieManager.getInstance().removeAllCookies(null); + CookieManager.getInstance().flush(); + } else { + CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(context); + cookieSyncMngr.startSync(); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.removeAllCookie(); + cookieManager.removeSessionCookie(); + cookieSyncMngr.stopSync(); + cookieSyncMngr.sync(); + } + } + + private static String redirectUserToAuthorizeAndLogin(String clientId, String instance) { + String queryString = Helper.CLIENT_ID + "=" + clientId; + queryString += "&" + Helper.REDIRECT_URI + "=" + Uri.encode(Helper.REDIRECT_CONTENT_WEB); + queryString += "&response_type=code"; + queryString += "&scope=read write follow"; + return "https://" + instance + "/oauth/authorize?" + queryString; + } + + @SuppressLint("SetJavaScriptEnabled") + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_webview_connect); + Bundle b = getIntent().getExtras(); + if (b != null) { + instance = b.getString("instance"); + clientId = b.getString("client_id"); + clientSecret = b.getString("client_secret"); + software = b.getString("software"); + } + if (instance == null) + finish(); + + webView = findViewById(R.id.webviewConnect); + clearCookies(MastodonWebviewConnectActivity.this); + webView.getSettings().setJavaScriptEnabled(true); + + if (Build.VERSION.SDK_INT >= 21) { + CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true); + } else { + CookieManager.getInstance().setAcceptCookie(true); + } + + final ProgressBar pbar = findViewById(R.id.progress_bar); + webView.setWebChromeClient(new WebChromeClient() { + @Override + public void onProgressChanged(WebView view, int progress) { + if (progress < 100 && pbar.getVisibility() == ProgressBar.GONE) { + pbar.setVisibility(ProgressBar.VISIBLE); + } + pbar.setProgress(progress); + if (progress == 100) { + pbar.setVisibility(ProgressBar.GONE); + } + } + }); + + if (instance == null) { + finish(); + } + webView.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + super.shouldOverrideUrlLoading(view, url); + if (url.contains(Helper.REDIRECT_CONTENT_WEB)) { + String[] val = url.split("code="); + if (val.length < 2) { + Toasty.error(MastodonWebviewConnectActivity.this, getString(R.string.toast_code_error), Toast.LENGTH_LONG).show(); + Intent myIntent = new Intent(MastodonWebviewConnectActivity.this, LoginActivity.class); + startActivity(myIntent); + finish(); + return false; + } + String code = val[1]; + OauthParams oauthParams = new OauthParams(); + oauthParams.setClient_id(clientId); + oauthParams.setClient_secret(clientSecret); + oauthParams.setGrant_type("authorization_code"); + oauthParams.setCode(code); + oauthParams.setRedirect_uri(Helper.REDIRECT_CONTENT_WEB); + + new Thread(() -> { + try { + Token token = new RetrofitMastodonAPI(MastodonWebviewConnectActivity.this, instance, null).manageToken(oauthParams); + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token.getAccess_token()); + editor.apply(); + new RetrofitMastodonAPI(MastodonWebviewConnectActivity.this, instance, token.getAccess_token()).updateCredential(MastodonWebviewConnectActivity.this, clientId, clientSecret, token.getRefresh_token(), software); + } catch (Exception | Error ignored) { + } + }).start(); + return true; + } + return false; + } + + }); + webView.loadUrl(redirectUserToAuthorizeAndLogin(clientId, instance)); + } + + @Override + public void onBackPressed() { + if (webView != null && webView.canGoBack()) { + webView.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (alert != null) { + alert.dismiss(); + alert = null; + } + if (webView != null) { + webView.destroy(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/fedilabtube/client/MastodonService.java b/app/src/main/java/app/fedilab/fedilabtube/client/MastodonService.java index ff8f822..7becc1c 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/client/MastodonService.java +++ b/app/src/main/java/app/fedilab/fedilabtube/client/MastodonService.java @@ -28,20 +28,23 @@ import retrofit2.http.Query; interface MastodonService { - @GET("apps") - Call getOauth(@Query("client_name") String client_name, @Query("redirect_uris") String redirect_uris, @Query("scopes") String scopes, @Query("website") String website); + @POST("apps") + Call getOauth( + @Query("client_name") String client_name, + @Query("redirect_uris") String redirect_uris, + @Query("scopes") String scopes, + @Query("website") String website); @FormUrlEncoded @POST("/oauth/token") Call createToken( + @Field("grant_type") String grant_type, @Field("client_id") String client_id, @Field("client_secret") String client_secret, - @Field("grant_type") String grant_type, - @Field("scrope") String scope, - @Field("username") String username, - @Field("password") String password); + @Field("redirect_uri") String redirect_uri, + @Field("code") String code); - @GET("/accounts/verify_credentials") + @GET("accounts/verify_credentials") Call verifyCredentials(@Header("Authorization") String credentials); } diff --git a/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitMastodonAPI.java b/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitMastodonAPI.java index b4ed5ca..a7d7c97 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitMastodonAPI.java +++ b/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitMastodonAPI.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; -import app.fedilab.fedilabtube.BuildConfig; import app.fedilab.fedilabtube.MainActivity; import app.fedilab.fedilabtube.R; import app.fedilab.fedilabtube.client.data.AccountData; @@ -20,9 +19,7 @@ import app.fedilab.fedilabtube.client.entities.Error; import app.fedilab.fedilabtube.client.entities.Oauth; import app.fedilab.fedilabtube.client.entities.OauthParams; import app.fedilab.fedilabtube.client.entities.Token; -import app.fedilab.fedilabtube.client.entities.UserMe; import app.fedilab.fedilabtube.helper.Helper; -import app.fedilab.fedilabtube.helper.HelperInstance; import app.fedilab.fedilabtube.sqlite.AccountDAO; import app.fedilab.fedilabtube.sqlite.Sqlite; import retrofit2.Call; @@ -34,14 +31,9 @@ public class RetrofitMastodonAPI { private final String finalUrl; private final Context _context; - private final String instance; + private String instance; private String token; - public RetrofitMastodonAPI(Context context) { - _context = context; - instance = HelperInstance.getLiveInstance(context); - finalUrl = "https://" + HelperInstance.getLiveInstance(context) + "/api/v1/"; - } public RetrofitMastodonAPI(Context context, String instance, String token) { _context = context; @@ -50,10 +42,9 @@ public class RetrofitMastodonAPI { finalUrl = "https://" + instance + "/api/v1/"; } - public static void updateCredential(Activity activity, String token, String client_id, String client_secret, String refresh_token, String host, String software) { + public void updateCredential(Activity activity, String client_id, String client_secret, String refresh_token, String software) { new Thread(() -> { AccountData.Account account; - String instance = host; try { account = new RetrofitMastodonAPI(activity, instance, token).verifyCredentials(); } catch (Error error) { @@ -118,6 +109,7 @@ public class RetrofitMastodonAPI { Call oauth; oauth = mastodonService.getOauth(client_name, redirect_uris, scopes, website); Response response = oauth.execute(); + if (response.isSuccessful() && response.body() != null) { return response.body(); } @@ -164,7 +156,13 @@ public class RetrofitMastodonAPI { */ public Token manageToken(OauthParams oauthParams) throws Error { MastodonService mastodonService = init(); - Call createToken = mastodonService.createToken(oauthParams.getClient_id(), oauthParams.getClient_secret(), oauthParams.getGrant_type(), oauthParams.getScope(), oauthParams.getUsername(), oauthParams.getPassword()); + Call createToken = mastodonService.createToken( + oauthParams.getGrant_type(), + oauthParams.getClient_id(), + oauthParams.getClient_secret(), + oauthParams.getRedirect_uri(), + oauthParams.getCode() + ); if (createToken != null) { try { Response response = createToken.execute(); diff --git a/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitPeertubeAPI.java b/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitPeertubeAPI.java index 082df60..1aad1d3 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitPeertubeAPI.java +++ b/app/src/main/java/app/fedilab/fedilabtube/client/RetrofitPeertubeAPI.java @@ -99,9 +99,9 @@ public class RetrofitPeertubeAPI { private final Context _context; private final String instance; private final String count; + private final String showNSFWVideos; private String token; private Set selection; - private final String showNSFWVideos; public RetrofitPeertubeAPI(Context context) { _context = context; diff --git a/app/src/main/java/app/fedilab/fedilabtube/client/entities/OauthParams.java b/app/src/main/java/app/fedilab/fedilabtube/client/entities/OauthParams.java index 0f2bc53..fc1ab4b 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/client/entities/OauthParams.java +++ b/app/src/main/java/app/fedilab/fedilabtube/client/entities/OauthParams.java @@ -40,6 +40,10 @@ public class OauthParams { private String access_token; @SerializedName("response_type") private String response_type; + @SerializedName("code") + private String code; + @SerializedName("redirect_uri") + private String redirect_uri; public String getClient_secret() { return client_secret; @@ -120,4 +124,20 @@ public class OauthParams { public void setResponse_type(String response_type) { this.response_type = response_type; } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getRedirect_uri() { + return redirect_uri; + } + + public void setRedirect_uri(String redirect_uri) { + this.redirect_uri = redirect_uri; + } } diff --git a/app/src/main/java/app/fedilab/fedilabtube/helper/Helper.java b/app/src/main/java/app/fedilab/fedilabtube/helper/Helper.java index f6b3e09..47f6331 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/helper/Helper.java +++ b/app/src/main/java/app/fedilab/fedilabtube/helper/Helper.java @@ -102,12 +102,14 @@ public class Helper { public static final String SET_SHARE_DETAILS = "set_share_details"; public static final String NOTIFICATION_INTERVAL = "notification_interval"; public static final String LAST_NOTIFICATION_READ = "last_notification_read"; + public static final String REDIRECT_CONTENT_WEB = "tubelab://backtotubelab"; public static final int DEFAULT_VIDEO_CACHE_MB = 100; @SuppressWarnings({"unused", "RedundantSuppression"}) public static final String TAG = "mastodon_etalab"; public static final String ID = "id"; public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; + public static final String REDIRECT_URI = "redirect_uri"; public static final String WEBSITE_VALUE = "https://fedilab.app"; public static final String CLIENT_NAME_VALUE = "TubeLab"; public static final String OAUTH_SCOPES_PEERTUBE = "openid profile"; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e9e2d9..1be51db 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -453,5 +453,6 @@ Instance is not available! The video should not have more than 5 tags! Watermark + An error occurred! The instance did not return an authorisation code! \ No newline at end of file