diff options
author | Stefan Niedermann <info@niedermann.it> | 2020-06-20 13:55:36 +0300 |
---|---|---|
committer | Stefan Niedermann <info@niedermann.it> | 2020-06-20 13:55:36 +0300 |
commit | 7ca8fe14a3d63c0ab0f733a79ba1a3c87a47dd59 (patch) | |
tree | 03bbaf67ba097d27fa55c2238e2f42d2b526e4a3 /glide-sso-integration | |
parent | a914ff3a32f2c5e81178fc60bd29b4c04053609b (diff) |
Move SSO Glide integration into own module
Diffstat (limited to 'glide-sso-integration')
9 files changed, 392 insertions, 0 deletions
diff --git a/glide-sso-integration/.gitignore b/glide-sso-integration/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/glide-sso-integration/.gitignore @@ -0,0 +1 @@ +/build
\ No newline at end of file diff --git a/glide-sso-integration/build.gradle b/glide-sso-integration/build.gradle new file mode 100644 index 000000000..1f8cddf59 --- /dev/null +++ b/glide-sso-integration/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + // Nextcloud SSO + implementation "com.github.nextcloud:Android-SingleSignOn:0.5.1" + + // Glide + implementation 'com.github.bumptech.glide:glide:4.11.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' + + implementation fileTree(dir: "libs", include: ["*.jar"]) +}
\ No newline at end of file diff --git a/glide-sso-integration/consumer-rules.pro b/glide-sso-integration/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/glide-sso-integration/consumer-rules.pro diff --git a/glide-sso-integration/proguard-rules.pro b/glide-sso-integration/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/glide-sso-integration/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile
\ No newline at end of file diff --git a/glide-sso-integration/src/main/AndroidManifest.xml b/glide-sso-integration/src/main/AndroidManifest.xml new file mode 100644 index 000000000..88728afb3 --- /dev/null +++ b/glide-sso-integration/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ +<manifest package="it.niedermann.android.glidesso"> + + / +</manifest>
\ No newline at end of file diff --git a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnLibraryGlideModule.java b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnLibraryGlideModule.java new file mode 100644 index 000000000..6669d5da1 --- /dev/null +++ b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnLibraryGlideModule.java @@ -0,0 +1,33 @@ +package it.niedermann.android.glidesso; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.Registry; +import com.bumptech.glide.annotation.GlideModule; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.module.LibraryGlideModule; + +import java.io.InputStream; + +/** + * Registers OkHttp related classes via Glide's annotation processor. + * + * <p>For Applications that depend on this library and include an {@link LibraryGlideModule} and Glide's + * annotation processor, this class will be automatically included. + */ +@GlideModule +public final class SingleSignOnLibraryGlideModule extends LibraryGlideModule { + + private static final String TAG = SingleSignOnLibraryGlideModule.class.getSimpleName(); + + @Override + public void registerComponents( + @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { + Log.v(TAG, "Replacing default implementation for " + GlideUrl.class.getSimpleName() + "."); + registry.replace(GlideUrl.class, InputStream.class, new SingleSignOnUrlLoader.Factory(context)); + } +} diff --git a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnStreamFetcher.java b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnStreamFetcher.java new file mode 100644 index 000000000..10f708890 --- /dev/null +++ b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnStreamFetcher.java @@ -0,0 +1,145 @@ +package it.niedermann.android.glidesso; + +import android.content.Context; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.model.GlideUrl; +import com.google.gson.GsonBuilder; +import com.nextcloud.android.sso.AccountImporter; +import com.nextcloud.android.sso.aidl.NextcloudRequest; +import com.nextcloud.android.sso.api.NextcloudAPI; +import com.nextcloud.android.sso.api.Response; +import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; +import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; +import com.nextcloud.android.sso.exceptions.TokenMismatchException; +import com.nextcloud.android.sso.helper.SingleAccountHelper; +import com.nextcloud.android.sso.model.SingleSignOnAccount; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Fetches an {@link InputStream} using the Nextcloud SSO library. + */ +public class SingleSignOnStreamFetcher implements DataFetcher<InputStream> { + + /** + * Use this header and set the {@link SingleSignOnAccount} name property as value + * Format of the value needs to be + */ + public static final String X_HEADER_SSO_ACCOUNT_NAME = "X-SSO-Account-Name"; + + private static final String TAG = SingleSignOnStreamFetcher.class.getSimpleName(); + private static final String METHOD_GET = "GET"; + + private static final Map<String, NextcloudAPI> INITIALIZED_APIs = new HashMap<>(); + + private final Context context; + private final GlideUrl url; + + // Public API. + @SuppressWarnings("WeakerAccess") + public SingleSignOnStreamFetcher(Context context, GlideUrl url) { + this.context = context; + this.url = url; + } + + @Override + public void loadData(@NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) { + NextcloudAPI client; + try { + final SingleSignOnAccount ssoAccount; + if (url.getHeaders().containsKey(X_HEADER_SSO_ACCOUNT_NAME)) { + ssoAccount = AccountImporter.getSingleSignOnAccount(context, url.getHeaders().get(X_HEADER_SSO_ACCOUNT_NAME)); + } else { + ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context); + } + client = INITIALIZED_APIs.get(ssoAccount.name); + boolean didInitialize = false; + if (client == null) { + client = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() { + @Override + public void onConnected() { + Log.v(TAG, "SSO API successfully initialized"); + } + + @Override + public void onError(Exception ex) { + Log.e(TAG, ex.getMessage(), ex); + } + }); + INITIALIZED_APIs.put(ssoAccount.name, client); + didInitialize = true; + } + + NextcloudRequest.Builder requestBuilder; + try { + requestBuilder = new NextcloudRequest.Builder() + .setMethod(METHOD_GET) + .setUrl(url.toURL().getPath()); + Map<String, List<String>> header = new HashMap<>(); + for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) { + if (!X_HEADER_SSO_ACCOUNT_NAME.equals(headerEntry.getKey())) { + header.put(headerEntry.getKey(), Collections.singletonList(headerEntry.getValue())); + } + } + requestBuilder.setHeader(header); + NextcloudRequest nextcloudRequest = requestBuilder.build(); + Log.v(TAG, nextcloudRequest.toString()); + Response response = client.performNetworkRequestV2(nextcloudRequest); + callback.onDataReady(response.getBody()); + } catch (MalformedURLException e) { + callback.onLoadFailed(e); + } catch (TokenMismatchException e) { + if (!didInitialize) { + Log.w(TAG, "SSO Glide loader failed with TokenMismatchException, trying to re-initialize..."); + client.stop(); + INITIALIZED_APIs.remove(ssoAccount.name); + loadData(priority, callback); + } else { + e.printStackTrace(); + callback.onLoadFailed(e); + } + } catch (Exception e) { + callback.onLoadFailed(e); + } + + } catch (NextcloudFilesAppAccountNotFoundException e) { + e.printStackTrace(); + } catch (NoCurrentAccountSelectedException e) { + e.printStackTrace(); + } + } + + @Override + public void cleanup() { + + } + + @Override + public void cancel() { + + } + + @NonNull + @Override + public Class<InputStream> getDataClass() { + return InputStream.class; + } + + @NonNull + @Override + public DataSource getDataSource() { + return DataSource.REMOTE; + } +} diff --git a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrl.java b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrl.java new file mode 100644 index 000000000..b8f359757 --- /dev/null +++ b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrl.java @@ -0,0 +1,88 @@ +package it.niedermann.android.glidesso; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.Headers; +import com.bumptech.glide.load.model.LazyHeaders; +import com.nextcloud.android.sso.helper.SingleAccountHelper; +import com.nextcloud.android.sso.model.SingleSignOnAccount; + +import java.net.URL; +import java.util.Map; + +import static it.niedermann.android.glidesso.SingleSignOnStreamFetcher.X_HEADER_SSO_ACCOUNT_NAME; + +/** + * Use this as kind of {@link GlideUrl} if you want to do a {@link Glide} request from a {@link SingleSignOnAccount} which is not set by {@link SingleAccountHelper#setCurrentAccount(Context, String)}. + */ +public class SingleSignOnUrl extends GlideUrl { + + public SingleSignOnUrl(@NonNull SingleSignOnAccount ssoAccount, @NonNull String url) { + this(ssoAccount.name, url); + } + + public SingleSignOnUrl(@NonNull SingleSignOnAccount ssoAccount, @NonNull URL url) { + this(ssoAccount.name, url); + } + + public SingleSignOnUrl(@NonNull SingleSignOnAccount ssoAccount, @NonNull String url, @NonNull Headers headers) { + this(ssoAccount.name, url, headers); + } + + public SingleSignOnUrl(@NonNull SingleSignOnAccount ssoAccount, @NonNull URL url, @NonNull Headers headers) { + this(ssoAccount.name, url, headers); + } + + public SingleSignOnUrl(@NonNull String ssoAccountName, @NonNull String url) { + super(url, new SingleSignOnOriginHeader(ssoAccountName)); + } + + public SingleSignOnUrl(@NonNull String ssoAccountName, @NonNull URL url) { + super(url, new SingleSignOnOriginHeader(ssoAccountName)); + } + + public SingleSignOnUrl(@NonNull String ssoAccountName, @NonNull String url, @NonNull Headers headers) { + super(url, new SingleSignOnOriginHeader(ssoAccountName, headers)); + } + + public SingleSignOnUrl(@NonNull String ssoAccountName, @NonNull URL url, @NonNull Headers headers) { + super(url, new SingleSignOnOriginHeader(ssoAccountName, headers)); + } + + private static class SingleSignOnOriginHeader implements Headers { + + private Headers headers; + + /** + * Use this as {@link Headers} if you want to do a {@link Glide} request for an {@link SingleSignOnAccount} which is not set by {@link SingleAccountHelper} as current {@link SingleSignOnAccount}. + * + * @param ssoAccountName Account name from which host the request should be fired (needs to match {@link SingleSignOnAccount#name}) + */ + public SingleSignOnOriginHeader(@NonNull String ssoAccountName) { + this.headers = new LazyHeaders.Builder().addHeader(X_HEADER_SSO_ACCOUNT_NAME, ssoAccountName).build(); + } + + /** + * Use this as {@link Headers} if you want to do a {@link Glide} request for an {@link SingleSignOnAccount} which is not set by {@link SingleAccountHelper} as current {@link SingleSignOnAccount}. + * + * @param ssoAccountName Account name from which host the request should be fired (needs to match {@link SingleSignOnAccount#name}) + */ + public SingleSignOnOriginHeader(@NonNull String ssoAccountName, Headers headers) { + LazyHeaders.Builder builder = new LazyHeaders.Builder(); + for (Map.Entry<String, String> entry : headers.getHeaders().entrySet()) { + builder.addHeader(entry.getKey(), entry.getValue()); + } + builder.addHeader(X_HEADER_SSO_ACCOUNT_NAME, ssoAccountName).build(); + this.headers = builder.build(); + } + + @Override + public Map<String, String> getHeaders() { + return this.headers.getHeaders(); + } + } +} diff --git a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrlLoader.java b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrlLoader.java new file mode 100644 index 000000000..10f990b3e --- /dev/null +++ b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrlLoader.java @@ -0,0 +1,66 @@ +package it.niedermann.android.glidesso; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.model.GlideUrl; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.MultiModelLoaderFactory; + +import java.io.InputStream; + +/** + * A simple model loader for fetching media over http/https using OkHttp. + */ +public class SingleSignOnUrlLoader implements ModelLoader<GlideUrl, InputStream> { + + private static final String TAG = SingleSignOnUrlLoader.class.getSimpleName(); + private final Context context; + + // Public API. + @SuppressWarnings("WeakerAccess") + public SingleSignOnUrlLoader(@NonNull Context context) { + this.context = context; + } + + @Override + public boolean handles(@NonNull GlideUrl url) { + return true; + } + + @Override + public LoadData<InputStream> buildLoadData( + @NonNull GlideUrl model, int width, int height, @NonNull Options options) { + return new LoadData<>(model, new SingleSignOnStreamFetcher(context, model)); + } + + /** + * The default factory for {@link SingleSignOnUrlLoader}s. + */ + // Public API. + @SuppressWarnings("WeakerAccess") + public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> { + private SingleSignOnUrlLoader loader; + + /** + * Constructor for a new Factory that runs requests using given client. + */ + public Factory(@NonNull Context context) { + loader = new SingleSignOnUrlLoader(context); + } + + @NonNull + @Override + public ModelLoader<GlideUrl, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) { + return loader; + } + + @Override + public void teardown() { + // Do nothing, this instance doesn't own the client. + } + } +} |