Loading .gitlab-ci.yml +3 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,9 @@ stages: before_script: - echo email.key=$PEPPER >> local.properties - echo GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID >> local.properties - echo GOOGLE_REDIRECT_URI=$GOOGLE_REDIRECT_URI >> local.properties - echo YAHOO_CLIENT_ID=$YAHOO_CLIENT_ID >> local.properties - export GRADLE_USER_HOME=$(pwd)/.gradle - chmod +x ./gradlew Loading app/build.gradle +11 −5 Original line number Diff line number Diff line Loading @@ -86,7 +86,7 @@ android { shrinkResources true buildConfigField "String", "EMAIL_KEY", "\"${retrieveEmailKey()}\"" buildConfigField "String", "EMAIL_KEY", "\"${retrieveKey("email.key")}\"" } } Loading @@ -101,8 +101,14 @@ android { } defaultConfig { buildConfigField "String", "GOOGLE_CLIENT_ID", "\"${retrieveKey("GOOGLE_CLIENT_ID")}\"" buildConfigField "String", "GOOGLE_REDIRECT_URI", "\"${retrieveKey("GOOGLE_REDIRECT_URI")}\"" buildConfigField "String", "YAHOO_CLIENT_ID", "\"${retrieveKey("YAHOO_CLIENT_ID")}\"" manifestPlaceholders = [ 'appAuthRedirectScheme': 'net.openid.appauthdemo' 'appAuthRedirectScheme': applicationId, "googleAuthRedirectScheme": retrieveKey("GOOGLE_REDIRECT_URI") ] } } Loading Loading @@ -195,13 +201,13 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}" } def retrieveEmailKey() { def retrieveKey(String keyName) { Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) String value = properties.getProperty("email.key") String value = properties.getProperty(keyName) if (value == null) { throw new GradleException("email.key property not found in local.properties file") throw new GradleException(keyName + " property not found in local.properties file") } return value Loading app/src/main/AndroidManifest.xml +2 −2 Original line number Diff line number Diff line Loading @@ -567,8 +567,8 @@ <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="net.openid.appauthdemo" /> <data android:scheme="com.googleusercontent.apps.100496780587-pbiu5eudcjm6cge2phduc6mt8mgbsmsr" /> <data android:scheme="${appAuthRedirectScheme}" /> <data android:scheme="${googleAuthRedirectScheme}" /> </intent-filter> </activity> Loading app/src/main/java/at/bitfire/davdroid/OpenIdUtils.kt 0 → 100644 +32 −0 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2023 * 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. * * This program 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 this program. If not, see <https://www.gnu.org/licenses/>. */ package at.bitfire.davdroid import net.openid.appauth.ClientAuthentication import net.openid.appauth.ClientSecretBasic import net.openid.appauth.NoClientAuthentication object OpenIdUtils { fun getClientAuthentication(secret: String?): ClientAuthentication { if (secret == null) { return NoClientAuthentication.INSTANCE } return ClientSecretBasic(secret) } } app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java +58 −158 Original line number Diff line number Diff line /* * Copyright ECORP SAS 2022 * Copyright MURENA SAS 2022, 2023 * 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 Loading @@ -16,200 +16,108 @@ package at.bitfire.davdroid.authorization; import android.content.Context; import android.content.res.Resources; import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import net.openid.appauth.AuthorizationServiceConfiguration; import net.openid.appauth.AuthorizationServiceConfiguration.RetrieveConfigurationCallback; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import at.bitfire.davdroid.R; import at.bitfire.davdroid.BuildConfig; /** * An abstraction of identity providers, containing all necessary info for the demo app. */ public class IdentityProvider { /** * Value used to indicate that a configured property is not specified or required. */ public static final int NOT_SPECIFIED = -1; public static final IdentityProvider GOOGLE = new IdentityProvider( "Google", R.string.google_discovery_uri, NOT_SPECIFIED, // auth endpoint is discovered NOT_SPECIFIED, // token endpoint is discovered R.string.google_client_id, NOT_SPECIFIED, // client secret is not required for Google R.string.google_auth_redirect_uri, R.string.google_scope_string, R.string.google_name); public static final List<IdentityProvider> PROVIDERS = Arrays.asList( GOOGLE); public static List<IdentityProvider> getEnabledProviders(Context context) { ArrayList<IdentityProvider> providers = new ArrayList<>(); for (IdentityProvider provider : PROVIDERS) { provider.readConfiguration(context); providers.add(provider); } return providers; } @NonNull public final String name; @StringRes public final int buttonContentDescriptionRes; @StringRes private final int mDiscoveryEndpointRes; "https://accounts.google.com/.well-known/openid-configuration", null, null, BuildConfig.GOOGLE_CLIENT_ID, null, BuildConfig.GOOGLE_REDIRECT_URI + ":/oauth2redirect", "openid profile email https://www.googleapis.com/auth/carddav https://www.googleapis.com/auth/calendar https://mail.google.com/", null ); @StringRes private final int mAuthEndpointRes; @StringRes private final int mTokenEndpointRes; @Nullable private final Uri mDiscoveryEndpoint; @StringRes private final int mClientIdRes; @Nullable private final Uri mAuthEndpoint; @StringRes private final int mClientSecretRes; @Nullable private final Uri mTokenEndpoint; @NonNull private final String mClientId; @StringRes private final int mRedirectUriRes; @Nullable private final String mClientSecret; @NonNull private final Uri mRedirectUri; @StringRes private final int mScopeRes; @Nullable private final String mScope; private boolean mConfigurationRead = false; private Uri mDiscoveryEndpoint; private Uri mAuthEndpoint; private Uri mTokenEndpoint; private String mClientId; private String mClientSecret; private Uri mRedirectUri; private String mScope; @Nullable private final String mUserInfoEndpoint; IdentityProvider( @NonNull String name, @StringRes int discoveryEndpointRes, @StringRes int authEndpointRes, @StringRes int tokenEndpointRes, @StringRes int clientIdRes, @StringRes int clientSecretRes, @StringRes int redirectUriRes, @StringRes int scopeRes, @StringRes int buttonContentDescriptionRes) { if (!isSpecified(discoveryEndpointRes) && !isSpecified(authEndpointRes) && !isSpecified(tokenEndpointRes)) { @Nullable String discoveryEndpoint, @Nullable String authEndpoint, @Nullable String tokenEndpoint, @NonNull String clientId, @Nullable String clientSecret, @NonNull String redirectUri, @Nullable String scope, @Nullable String userInfoEndpoint) { if (discoveryEndpoint == null && (authEndpoint == null || tokenEndpoint == null)) { throw new IllegalArgumentException( "the discovery endpoint or the auth and token endpoints must be specified"); } this.name = name; this.mDiscoveryEndpointRes = discoveryEndpointRes; this.mAuthEndpointRes = authEndpointRes; this.mTokenEndpointRes = tokenEndpointRes; this.mClientIdRes = checkSpecified(clientIdRes, "clientIdRes"); this.mClientSecretRes = clientSecretRes; this.mRedirectUriRes = checkSpecified(redirectUriRes, "redirectUriRes"); this.mScopeRes = checkSpecified(scopeRes, "scopeRes"); this.buttonContentDescriptionRes = checkSpecified(buttonContentDescriptionRes, "buttonContentDescriptionRes"); } /** * This must be called before any of the getters will function. */ public void readConfiguration(Context context) { if (mConfigurationRead) { return; } Resources res = context.getResources(); mDiscoveryEndpoint = isSpecified(mDiscoveryEndpointRes) ? getUriResource(res, mDiscoveryEndpointRes, "discoveryEndpointRes") : null; mAuthEndpoint = isSpecified(mAuthEndpointRes) ? getUriResource(res, mAuthEndpointRes, "authEndpointRes") : null; mTokenEndpoint = isSpecified(mTokenEndpointRes) ? getUriResource(res, mTokenEndpointRes, "tokenEndpointRes") : null; mClientId = res.getString(mClientIdRes); mClientSecret = isSpecified(mClientSecretRes) ? res.getString(mClientSecretRes) : null; mRedirectUri = getUriResource(res, mRedirectUriRes, "mRedirectUriRes"); mScope = res.getString(mScopeRes); mConfigurationRead = true; } private void checkConfigurationRead() { if (!mConfigurationRead) { throw new IllegalStateException("Configuration not read"); } } @Nullable public Uri getDiscoveryEndpoint() { checkConfigurationRead(); return mDiscoveryEndpoint; } @Nullable public Uri getAuthEndpoint() { checkConfigurationRead(); return mAuthEndpoint; } @Nullable public Uri getTokenEndpoint() { checkConfigurationRead(); return mTokenEndpoint; this.mDiscoveryEndpoint = retrieveUri(discoveryEndpoint); this.mAuthEndpoint = retrieveUri(authEndpoint); this.mTokenEndpoint = retrieveUri(tokenEndpoint); this.mClientId = clientId; this.mClientSecret = clientSecret; this.mRedirectUri = Objects.requireNonNull(retrieveUri(redirectUri)); this.mScope = scope; this.mUserInfoEndpoint = userInfoEndpoint; } @NonNull public String getClientId() { checkConfigurationRead(); return mClientId; } @Nullable public String getClientSecret() { checkConfigurationRead(); return mClientSecret; } @NonNull public Uri getRedirectUri() { checkConfigurationRead(); return mRedirectUri; } @NonNull public String getScope() { checkConfigurationRead(); return mScope; } public void retrieveConfig(Context context, RetrieveConfigurationCallback callback) { readConfiguration(context); if (getDiscoveryEndpoint() != null) { @Nullable public String getUserInfoEndpoint() { return mUserInfoEndpoint; } public void retrieveConfig(RetrieveConfigurationCallback callback) { if (mDiscoveryEndpoint != null) { AuthorizationServiceConfiguration.fetchFromUrl(mDiscoveryEndpoint, callback); } else { AuthorizationServiceConfiguration config = Loading @@ -218,19 +126,11 @@ public class IdentityProvider { } } private static boolean isSpecified(int value) { return value != NOT_SPECIFIED; } private static int checkSpecified(int value, String valueName) { if (value == NOT_SPECIFIED) { throw new IllegalArgumentException(valueName + " must be specified"); @Nullable private Uri retrieveUri(@Nullable String value) { if (value == null) { return null; } return value; return Uri.parse(value); } private static Uri getUriResource(Resources res, @StringRes int resId, String resName) { return Uri.parse(res.getString(resId)); } } Loading
.gitlab-ci.yml +3 −0 Original line number Diff line number Diff line Loading @@ -6,6 +6,9 @@ stages: before_script: - echo email.key=$PEPPER >> local.properties - echo GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID >> local.properties - echo GOOGLE_REDIRECT_URI=$GOOGLE_REDIRECT_URI >> local.properties - echo YAHOO_CLIENT_ID=$YAHOO_CLIENT_ID >> local.properties - export GRADLE_USER_HOME=$(pwd)/.gradle - chmod +x ./gradlew Loading
app/build.gradle +11 −5 Original line number Diff line number Diff line Loading @@ -86,7 +86,7 @@ android { shrinkResources true buildConfigField "String", "EMAIL_KEY", "\"${retrieveEmailKey()}\"" buildConfigField "String", "EMAIL_KEY", "\"${retrieveKey("email.key")}\"" } } Loading @@ -101,8 +101,14 @@ android { } defaultConfig { buildConfigField "String", "GOOGLE_CLIENT_ID", "\"${retrieveKey("GOOGLE_CLIENT_ID")}\"" buildConfigField "String", "GOOGLE_REDIRECT_URI", "\"${retrieveKey("GOOGLE_REDIRECT_URI")}\"" buildConfigField "String", "YAHOO_CLIENT_ID", "\"${retrieveKey("YAHOO_CLIENT_ID")}\"" manifestPlaceholders = [ 'appAuthRedirectScheme': 'net.openid.appauthdemo' 'appAuthRedirectScheme': applicationId, "googleAuthRedirectScheme": retrieveKey("GOOGLE_REDIRECT_URI") ] } } Loading Loading @@ -195,13 +201,13 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:${versions.okhttp}" } def retrieveEmailKey() { def retrieveKey(String keyName) { Properties properties = new Properties() properties.load(project.rootProject.file('local.properties').newDataInputStream()) String value = properties.getProperty("email.key") String value = properties.getProperty(keyName) if (value == null) { throw new GradleException("email.key property not found in local.properties file") throw new GradleException(keyName + " property not found in local.properties file") } return value Loading
app/src/main/AndroidManifest.xml +2 −2 Original line number Diff line number Diff line Loading @@ -567,8 +567,8 @@ <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="net.openid.appauthdemo" /> <data android:scheme="com.googleusercontent.apps.100496780587-pbiu5eudcjm6cge2phduc6mt8mgbsmsr" /> <data android:scheme="${appAuthRedirectScheme}" /> <data android:scheme="${googleAuthRedirectScheme}" /> </intent-filter> </activity> Loading
app/src/main/java/at/bitfire/davdroid/OpenIdUtils.kt 0 → 100644 +32 −0 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2023 * 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. * * This program 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 this program. If not, see <https://www.gnu.org/licenses/>. */ package at.bitfire.davdroid import net.openid.appauth.ClientAuthentication import net.openid.appauth.ClientSecretBasic import net.openid.appauth.NoClientAuthentication object OpenIdUtils { fun getClientAuthentication(secret: String?): ClientAuthentication { if (secret == null) { return NoClientAuthentication.INSTANCE } return ClientSecretBasic(secret) } }
app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java +58 −158 Original line number Diff line number Diff line /* * Copyright ECORP SAS 2022 * Copyright MURENA SAS 2022, 2023 * 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 Loading @@ -16,200 +16,108 @@ package at.bitfire.davdroid.authorization; import android.content.Context; import android.content.res.Resources; import android.net.Uri; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; import net.openid.appauth.AuthorizationServiceConfiguration; import net.openid.appauth.AuthorizationServiceConfiguration.RetrieveConfigurationCallback; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import at.bitfire.davdroid.R; import at.bitfire.davdroid.BuildConfig; /** * An abstraction of identity providers, containing all necessary info for the demo app. */ public class IdentityProvider { /** * Value used to indicate that a configured property is not specified or required. */ public static final int NOT_SPECIFIED = -1; public static final IdentityProvider GOOGLE = new IdentityProvider( "Google", R.string.google_discovery_uri, NOT_SPECIFIED, // auth endpoint is discovered NOT_SPECIFIED, // token endpoint is discovered R.string.google_client_id, NOT_SPECIFIED, // client secret is not required for Google R.string.google_auth_redirect_uri, R.string.google_scope_string, R.string.google_name); public static final List<IdentityProvider> PROVIDERS = Arrays.asList( GOOGLE); public static List<IdentityProvider> getEnabledProviders(Context context) { ArrayList<IdentityProvider> providers = new ArrayList<>(); for (IdentityProvider provider : PROVIDERS) { provider.readConfiguration(context); providers.add(provider); } return providers; } @NonNull public final String name; @StringRes public final int buttonContentDescriptionRes; @StringRes private final int mDiscoveryEndpointRes; "https://accounts.google.com/.well-known/openid-configuration", null, null, BuildConfig.GOOGLE_CLIENT_ID, null, BuildConfig.GOOGLE_REDIRECT_URI + ":/oauth2redirect", "openid profile email https://www.googleapis.com/auth/carddav https://www.googleapis.com/auth/calendar https://mail.google.com/", null ); @StringRes private final int mAuthEndpointRes; @StringRes private final int mTokenEndpointRes; @Nullable private final Uri mDiscoveryEndpoint; @StringRes private final int mClientIdRes; @Nullable private final Uri mAuthEndpoint; @StringRes private final int mClientSecretRes; @Nullable private final Uri mTokenEndpoint; @NonNull private final String mClientId; @StringRes private final int mRedirectUriRes; @Nullable private final String mClientSecret; @NonNull private final Uri mRedirectUri; @StringRes private final int mScopeRes; @Nullable private final String mScope; private boolean mConfigurationRead = false; private Uri mDiscoveryEndpoint; private Uri mAuthEndpoint; private Uri mTokenEndpoint; private String mClientId; private String mClientSecret; private Uri mRedirectUri; private String mScope; @Nullable private final String mUserInfoEndpoint; IdentityProvider( @NonNull String name, @StringRes int discoveryEndpointRes, @StringRes int authEndpointRes, @StringRes int tokenEndpointRes, @StringRes int clientIdRes, @StringRes int clientSecretRes, @StringRes int redirectUriRes, @StringRes int scopeRes, @StringRes int buttonContentDescriptionRes) { if (!isSpecified(discoveryEndpointRes) && !isSpecified(authEndpointRes) && !isSpecified(tokenEndpointRes)) { @Nullable String discoveryEndpoint, @Nullable String authEndpoint, @Nullable String tokenEndpoint, @NonNull String clientId, @Nullable String clientSecret, @NonNull String redirectUri, @Nullable String scope, @Nullable String userInfoEndpoint) { if (discoveryEndpoint == null && (authEndpoint == null || tokenEndpoint == null)) { throw new IllegalArgumentException( "the discovery endpoint or the auth and token endpoints must be specified"); } this.name = name; this.mDiscoveryEndpointRes = discoveryEndpointRes; this.mAuthEndpointRes = authEndpointRes; this.mTokenEndpointRes = tokenEndpointRes; this.mClientIdRes = checkSpecified(clientIdRes, "clientIdRes"); this.mClientSecretRes = clientSecretRes; this.mRedirectUriRes = checkSpecified(redirectUriRes, "redirectUriRes"); this.mScopeRes = checkSpecified(scopeRes, "scopeRes"); this.buttonContentDescriptionRes = checkSpecified(buttonContentDescriptionRes, "buttonContentDescriptionRes"); } /** * This must be called before any of the getters will function. */ public void readConfiguration(Context context) { if (mConfigurationRead) { return; } Resources res = context.getResources(); mDiscoveryEndpoint = isSpecified(mDiscoveryEndpointRes) ? getUriResource(res, mDiscoveryEndpointRes, "discoveryEndpointRes") : null; mAuthEndpoint = isSpecified(mAuthEndpointRes) ? getUriResource(res, mAuthEndpointRes, "authEndpointRes") : null; mTokenEndpoint = isSpecified(mTokenEndpointRes) ? getUriResource(res, mTokenEndpointRes, "tokenEndpointRes") : null; mClientId = res.getString(mClientIdRes); mClientSecret = isSpecified(mClientSecretRes) ? res.getString(mClientSecretRes) : null; mRedirectUri = getUriResource(res, mRedirectUriRes, "mRedirectUriRes"); mScope = res.getString(mScopeRes); mConfigurationRead = true; } private void checkConfigurationRead() { if (!mConfigurationRead) { throw new IllegalStateException("Configuration not read"); } } @Nullable public Uri getDiscoveryEndpoint() { checkConfigurationRead(); return mDiscoveryEndpoint; } @Nullable public Uri getAuthEndpoint() { checkConfigurationRead(); return mAuthEndpoint; } @Nullable public Uri getTokenEndpoint() { checkConfigurationRead(); return mTokenEndpoint; this.mDiscoveryEndpoint = retrieveUri(discoveryEndpoint); this.mAuthEndpoint = retrieveUri(authEndpoint); this.mTokenEndpoint = retrieveUri(tokenEndpoint); this.mClientId = clientId; this.mClientSecret = clientSecret; this.mRedirectUri = Objects.requireNonNull(retrieveUri(redirectUri)); this.mScope = scope; this.mUserInfoEndpoint = userInfoEndpoint; } @NonNull public String getClientId() { checkConfigurationRead(); return mClientId; } @Nullable public String getClientSecret() { checkConfigurationRead(); return mClientSecret; } @NonNull public Uri getRedirectUri() { checkConfigurationRead(); return mRedirectUri; } @NonNull public String getScope() { checkConfigurationRead(); return mScope; } public void retrieveConfig(Context context, RetrieveConfigurationCallback callback) { readConfiguration(context); if (getDiscoveryEndpoint() != null) { @Nullable public String getUserInfoEndpoint() { return mUserInfoEndpoint; } public void retrieveConfig(RetrieveConfigurationCallback callback) { if (mDiscoveryEndpoint != null) { AuthorizationServiceConfiguration.fetchFromUrl(mDiscoveryEndpoint, callback); } else { AuthorizationServiceConfiguration config = Loading @@ -218,19 +126,11 @@ public class IdentityProvider { } } private static boolean isSpecified(int value) { return value != NOT_SPECIFIED; } private static int checkSpecified(int value, String valueName) { if (value == NOT_SPECIFIED) { throw new IllegalArgumentException(valueName + " must be specified"); @Nullable private Uri retrieveUri(@Nullable String value) { if (value == null) { return null; } return value; return Uri.parse(value); } private static Uri getUriResource(Resources res, @StringRes int resId, String resName) { return Uri.parse(res.getString(resId)); } }