diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AccountAuthenticator.java b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AccountAuthenticator.java index 40580ed59..e22f2cd7c 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AccountAuthenticator.java +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/AccountAuthenticator.java @@ -345,6 +345,7 @@ private String refreshToken( String clientIdForRequest = null; String clientSecretForRequest = null; + String clientAuth = null; if (clientId == null) { Timber.d("Client Id not stored. Let's use the hardcoded one"); @@ -362,20 +363,29 @@ private String refreshToken( // Use token endpoint retrieved from oidc discovery tokenEndpoint = oidcServerConfigurationUseCaseResult.getDataOrNull().getTokenEndpoint(); + // RFC 7636: Public clients (token_endpoint_auth_method: none) must not send Authorization header if (oidcServerConfigurationUseCaseResult.getDataOrNull() != null && - oidcServerConfigurationUseCaseResult.getDataOrNull().isTokenEndpointAuthMethodSupportedClientSecretPost()) { + oidcServerConfigurationUseCaseResult.getDataOrNull().isTokenEndpointAuthMethodNone()) { + clientAuth = null; + clientIdForRequest = clientId; + } else if (oidcServerConfigurationUseCaseResult.getDataOrNull() != null && + oidcServerConfigurationUseCaseResult.getDataOrNull().isTokenEndpointAuthMethodSupportedClientSecretPost()) { + // For client_secret_post, credentials go in body, not Authorization header + clientAuth = null; clientIdForRequest = clientId; clientSecretForRequest = clientSecret; + } else { + // For other methods (e.g., client_secret_basic), use Basic auth header + clientAuth = OAuthUtils.Companion.getClientAuth(clientSecret, clientId); } } else { Timber.d("OIDC Discovery failed. Server discovery info: [ %s ]", oidcServerConfigurationUseCaseResult.getThrowableOrNull().toString()); tokenEndpoint = baseUrl + File.separator + mContext.getString(R.string.oauth2_url_endpoint_access); + clientAuth = OAuthUtils.Companion.getClientAuth(clientSecret, clientId); } - String clientAuth = OAuthUtils.Companion.getClientAuth(clientSecret, clientId); - String scope = mContext.getResources().getString(R.string.oauth2_openid_scope); TokenRequest oauthTokenRequest = new TokenRequest.RefreshToken( diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt index e926993f2..e89d2d8d7 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt @@ -604,28 +604,41 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted val clientRegistrationInfo = authenticationViewModel.registerClient.value?.peekContent()?.getStoredData() - val clientAuth = if (clientRegistrationInfo?.clientId != null && clientRegistrationInfo.clientSecret != null) { - OAuthUtils.getClientAuth(clientRegistrationInfo.clientSecret as String, clientRegistrationInfo.clientId) - - } else { - OAuthUtils.getClientAuth(getString(R.string.oauth2_client_secret), getString(R.string.oauth2_client_id)) - } - // Use oidc discovery one, or build an oauth endpoint using serverBaseUrl + Setup string. val tokenEndPoint: String var clientId: String? = null var clientSecret: String? = null + var clientAuth: String? = null val serverInfo = authenticationViewModel.serverInfo.value?.peekContent()?.getStoredData() if (serverInfo is ServerInfo.OIDCServer) { tokenEndPoint = serverInfo.oidcServerConfiguration.tokenEndpoint - if (serverInfo.oidcServerConfiguration.isTokenEndpointAuthMethodSupportedClientSecretPost()) { + + // RFC 7636: Public clients (token_endpoint_auth_method: none) must not send Authorization header + if (serverInfo.oidcServerConfiguration.isTokenEndpointAuthMethodNone()) { + clientAuth = null + clientId = clientRegistrationInfo?.clientId ?: contextProvider.getString(R.string.oauth2_client_id) + } else if (serverInfo.oidcServerConfiguration.isTokenEndpointAuthMethodSupportedClientSecretPost()) { + // For client_secret_post, credentials go in body, not Authorization header + clientAuth = null clientId = clientRegistrationInfo?.clientId ?: contextProvider.getString(R.string.oauth2_client_id) clientSecret = clientRegistrationInfo?.clientSecret ?: contextProvider.getString(R.string.oauth2_client_secret) + } else { + // For other methods (e.g., client_secret_basic), use Basic auth header + clientAuth = if (clientRegistrationInfo?.clientId != null && clientRegistrationInfo.clientSecret != null) { + OAuthUtils.getClientAuth(clientRegistrationInfo.clientSecret as String, clientRegistrationInfo.clientId) + } else { + OAuthUtils.getClientAuth(getString(R.string.oauth2_client_secret), getString(R.string.oauth2_client_id)) + } } } else { tokenEndPoint = "$serverBaseUrl${File.separator}${contextProvider.getString(R.string.oauth2_url_endpoint_access)}" + clientAuth = if (clientRegistrationInfo?.clientId != null && clientRegistrationInfo.clientSecret != null) { + OAuthUtils.getClientAuth(clientRegistrationInfo.clientSecret as String, clientRegistrationInfo.clientId) + } else { + OAuthUtils.getClientAuth(getString(R.string.oauth2_client_secret), getString(R.string.oauth2_client_id)) + } } val scope = resources.getString(R.string.oauth2_openid_scope) diff --git a/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/TokenRequestRemoteOperation.kt b/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/TokenRequestRemoteOperation.kt index 25064d141..cf29a5263 100644 --- a/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/TokenRequestRemoteOperation.kt +++ b/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/TokenRequestRemoteOperation.kt @@ -54,7 +54,9 @@ class TokenRequestRemoteOperation( val postMethod = PostMethod(URL(tokenRequestParams.tokenEndpoint), requestBody) - postMethod.addRequestHeader(AUTHORIZATION_HEADER, tokenRequestParams.clientAuth) + tokenRequestParams.clientAuth?.takeIf { it.isNotEmpty() }?.let { + postMethod.addRequestHeader(AUTHORIZATION_HEADER, it) + } val status = client.executeHttpMethod(postMethod) diff --git a/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/params/TokenRequestParams.kt b/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/params/TokenRequestParams.kt index 0aac1ce04..6fd61a707 100644 --- a/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/params/TokenRequestParams.kt +++ b/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/params/TokenRequestParams.kt @@ -30,7 +30,7 @@ import okhttp3.RequestBody sealed class TokenRequestParams( val tokenEndpoint: String, - val clientAuth: String, + val clientAuth: String?, val grantType: String, val scope: String, val clientId: String?, @@ -40,7 +40,7 @@ sealed class TokenRequestParams( class Authorization( tokenEndpoint: String, - clientAuth: String, + clientAuth: String?, grantType: String, scope: String, clientId: String?, @@ -65,7 +65,7 @@ sealed class TokenRequestParams( class RefreshToken( tokenEndpoint: String, - clientAuth: String, + clientAuth: String?, grantType: String, scope: String, clientId: String?, diff --git a/opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/OIDCServerConfiguration.kt b/opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/OIDCServerConfiguration.kt index d871569b5..395083067 100644 --- a/opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/OIDCServerConfiguration.kt +++ b/opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/OIDCServerConfiguration.kt @@ -35,4 +35,7 @@ data class OIDCServerConfiguration( ) { fun isTokenEndpointAuthMethodSupportedClientSecretPost(): Boolean = tokenEndpointAuthMethodsSupported?.any { it == "client_secret_post" } ?: false + + fun isTokenEndpointAuthMethodNone(): Boolean = + tokenEndpointAuthMethodsSupported?.any { it == "none" } ?: false } diff --git a/opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/TokenRequest.kt b/opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/TokenRequest.kt index 8b61ba59b..900a230db 100644 --- a/opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/TokenRequest.kt +++ b/opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/TokenRequest.kt @@ -24,7 +24,7 @@ package eu.opencloud.android.domain.authentication.oauth.model sealed class TokenRequest( val baseUrl: String, val tokenEndpoint: String, - val clientAuth: String, + val clientAuth: String?, val grantType: String, val scope: String, val clientId: String?, @@ -33,7 +33,7 @@ sealed class TokenRequest( class AccessToken( baseUrl: String, tokenEndpoint: String, - clientAuth: String, + clientAuth: String?, scope: String, clientId: String? = null, clientSecret: String? = null, @@ -45,7 +45,7 @@ sealed class TokenRequest( class RefreshToken( baseUrl: String, tokenEndpoint: String, - clientAuth: String, + clientAuth: String?, scope: String, clientId: String? = null, clientSecret: String? = null,