diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/AccountAuthenticator.java b/owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/AccountAuthenticator.java index bf32e5a6d23..c597bed732c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/AccountAuthenticator.java +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/AccountAuthenticator.java @@ -7,7 +7,7 @@ * @author Juan Carlos Garrote Gascón * * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2022 ownCloud GmbH. + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -343,6 +343,9 @@ private String refreshToken( String clientId = accountManager.getUserData(account, KEY_CLIENT_REGISTRATION_CLIENT_ID); String clientSecret = accountManager.getUserData(account, KEY_CLIENT_REGISTRATION_CLIENT_SECRET); + String clientIdForRequest = null; + String clientSecretForRequest = null; + if (clientId == null) { Timber.d("Client Id not stored. Let's use the hardcoded one"); clientId = mContext.getString(R.string.oauth2_client_id); @@ -359,6 +362,11 @@ private String refreshToken( // Use token endpoint retrieved from oidc discovery tokenEndpoint = oidcServerConfigurationUseCaseResult.getDataOrNull().getTokenEndpoint(); + if (oidcServerConfigurationUseCaseResult.getDataOrNull() != null && + oidcServerConfigurationUseCaseResult.getDataOrNull().isTokenEndpointAuthMethodSupportedClientSecretPost()) { + clientIdForRequest = clientId; + clientSecretForRequest = clientSecret; + } } else { Timber.d("OIDC Discovery failed. Server discovery info: [ %s ]", oidcServerConfigurationUseCaseResult.getThrowableOrNull().toString()); @@ -375,6 +383,8 @@ private String refreshToken( tokenEndpoint, clientAuth, scope, + clientIdForRequest, + clientSecretForRequest, refreshToken ); diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/LoginActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/LoginActivity.kt index 0a66264e815..4720c437baa 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/LoginActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/authentication/LoginActivity.kt @@ -609,9 +609,22 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted } // Use oidc discovery one, or build an oauth endpoint using serverBaseUrl + Setup string. - val tokenEndPoint = when (val serverInfo = authenticationViewModel.serverInfo.value?.peekContent()?.getStoredData()) { - is ServerInfo.OIDCServer -> serverInfo.oidcServerConfiguration.tokenEndpoint - else -> "$serverBaseUrl${File.separator}${contextProvider.getString(R.string.oauth2_url_endpoint_access)}" + val tokenEndPoint: String + + var clientId: String? = null + var clientSecret: String? = null + + when (val serverInfo = authenticationViewModel.serverInfo.value?.peekContent()?.getStoredData()) { + is ServerInfo.OIDCServer -> { + tokenEndPoint = serverInfo.oidcServerConfiguration.tokenEndpoint + if (serverInfo.oidcServerConfiguration.isTokenEndpointAuthMethodSupportedClientSecretPost()) { + clientId = clientRegistrationInfo?.clientId ?: contextProvider.getString(R.string.oauth2_client_id) + clientSecret = clientRegistrationInfo?.clientSecret ?: contextProvider.getString(R.string.oauth2_client_secret) + } + } + else -> { + tokenEndPoint = "$serverBaseUrl${File.separator}${contextProvider.getString(R.string.oauth2_url_endpoint_access)}" + } } val scope = resources.getString(R.string.oauth2_openid_scope) @@ -619,10 +632,12 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted val requestToken = TokenRequest.AccessToken( baseUrl = serverBaseUrl, tokenEndpoint = tokenEndPoint, - authorizationCode = authorizationCode, - redirectUri = OAuthUtils.buildRedirectUri(applicationContext).toString(), clientAuth = clientAuth, scope = scope, + clientId = clientId, + clientSecret = clientSecret, + authorizationCode = authorizationCode, + redirectUri = OAuthUtils.buildRedirectUri(applicationContext).toString(), codeVerifier = authenticationViewModel.codeVerifier ) diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpConstants.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpConstants.java index 48fcdcee2d7..ce889c7319a 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpConstants.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpConstants.java @@ -1,5 +1,5 @@ /* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. + * Copyright (C) 2024 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -59,6 +59,8 @@ public class HttpConstants { public static final String OAUTH_HEADER_REFRESH_TOKEN = "refresh_token"; public static final String OAUTH_HEADER_CODE_VERIFIER = "code_verifier"; public static final String OAUTH_HEADER_SCOPE = "scope"; + public static final String OAUTH_BODY_CLIENT_ID = "client_id"; + public static final String OAUTH_BODY_CLIENT_SECRET = "client_secret"; /*********************************************************************************************************** ************************************************ CONTENT TYPES ******************************************** diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/oauth/params/TokenRequestParams.kt b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/oauth/params/TokenRequestParams.kt index 6fb8919df7c..7169b75bb27 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/oauth/params/TokenRequestParams.kt +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/resources/oauth/params/TokenRequestParams.kt @@ -1,6 +1,6 @@ /* ownCloud Android Library is available under MIT license * - * Copyright (C) 2020 ownCloud GmbH. + * Copyright (C) 2024 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,6 +21,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.owncloud.android.lib.resources.oauth.params import com.owncloud.android.lib.common.http.HttpConstants @@ -32,6 +33,8 @@ sealed class TokenRequestParams( val clientAuth: String, val grantType: String, val scope: String, + val clientId: String?, + val clientSecret: String?, ) { abstract fun toRequestBody(): RequestBody @@ -40,19 +43,24 @@ sealed class TokenRequestParams( clientAuth: String, grantType: String, scope: String, + clientId: String?, + clientSecret: String?, val authorizationCode: String, val redirectUri: String, val codeVerifier: String, - ) : TokenRequestParams(tokenEndpoint, clientAuth, grantType, scope) { + ) : TokenRequestParams(tokenEndpoint, clientAuth, grantType, scope, clientId, clientSecret) { override fun toRequestBody(): RequestBody = - FormBody.Builder() - .add(HttpConstants.OAUTH_HEADER_AUTHORIZATION_CODE, authorizationCode) - .add(HttpConstants.OAUTH_HEADER_GRANT_TYPE, grantType) - .add(HttpConstants.OAUTH_HEADER_REDIRECT_URI, redirectUri) - .add(HttpConstants.OAUTH_HEADER_CODE_VERIFIER, codeVerifier) - .add(HttpConstants.OAUTH_HEADER_SCOPE, scope) - .build() + FormBody.Builder().apply { + add(HttpConstants.OAUTH_HEADER_AUTHORIZATION_CODE, authorizationCode) + add(HttpConstants.OAUTH_HEADER_GRANT_TYPE, grantType) + add(HttpConstants.OAUTH_HEADER_REDIRECT_URI, redirectUri) + add(HttpConstants.OAUTH_HEADER_CODE_VERIFIER, codeVerifier) + add(HttpConstants.OAUTH_HEADER_SCOPE, scope) + if (clientId != null) add(HttpConstants.OAUTH_BODY_CLIENT_ID, clientId) + if (clientSecret != null) add(HttpConstants.OAUTH_BODY_CLIENT_SECRET, clientSecret) + }.build() + } class RefreshToken( @@ -60,16 +68,18 @@ sealed class TokenRequestParams( clientAuth: String, grantType: String, scope: String, + clientId: String?, + clientSecret: String?, val refreshToken: String? = null - ) : TokenRequestParams(tokenEndpoint, clientAuth, grantType, scope) { + ) : TokenRequestParams(tokenEndpoint, clientAuth, grantType, scope, clientId, clientSecret) { override fun toRequestBody(): RequestBody = FormBody.Builder().apply { add(HttpConstants.OAUTH_HEADER_GRANT_TYPE, grantType) add(HttpConstants.OAUTH_HEADER_SCOPE, scope) - if (!refreshToken.isNullOrBlank()) { - add(HttpConstants.OAUTH_HEADER_REFRESH_TOKEN, refreshToken) - } + if (!refreshToken.isNullOrBlank()) add(HttpConstants.OAUTH_HEADER_REFRESH_TOKEN, refreshToken) + if (clientId != null) add(HttpConstants.OAUTH_BODY_CLIENT_ID, clientId) + if (clientSecret != null) add(HttpConstants.OAUTH_BODY_CLIENT_SECRET, clientSecret) }.build() } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/oauth/datasources/implementation/OCRemoteOAuthDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/oauth/datasources/implementation/OCRemoteOAuthDataSource.kt index 1cc057d611a..351f0ac80ba 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/oauth/datasources/implementation/OCRemoteOAuthDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/oauth/datasources/implementation/OCRemoteOAuthDataSource.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel García de Prada - * Copyright (C) 2020 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -103,6 +105,8 @@ class OCRemoteOAuthDataSource( authorizationCode = this.authorizationCode, grantType = this.grantType, scope = this.scope, + clientId = this.clientId, + clientSecret = this.clientSecret, redirectUri = this.redirectUri, clientAuth = this.clientAuth, codeVerifier = this.codeVerifier @@ -112,6 +116,8 @@ class OCRemoteOAuthDataSource( tokenEndpoint = this.tokenEndpoint, grantType = this.grantType, scope = this.scope, + clientId = this.clientId, + clientSecret = this.clientSecret, clientAuth = this.clientAuth, refreshToken = this.refreshToken ) diff --git a/owncloudData/src/test/java/com/owncloud/android/data/oauth/RemoteOAuthUtils.kt b/owncloudData/src/test/java/com/owncloud/android/data/oauth/RemoteOAuthUtils.kt index 6f6f09541a1..dc84a584620 100644 --- a/owncloudData/src/test/java/com/owncloud/android/data/oauth/RemoteOAuthUtils.kt +++ b/owncloudData/src/test/java/com/owncloud/android/data/oauth/RemoteOAuthUtils.kt @@ -48,6 +48,8 @@ val OC_REMOTE_TOKEN_REQUEST_PARAMS_ACCESS = TokenRequestParams.Authorization( clientAuth = OC_TOKEN_REQUEST_ACCESS.clientAuth, grantType = OC_TOKEN_REQUEST_ACCESS.grantType, scope = OC_TOKEN_REQUEST_ACCESS.scope, + clientId = null, + clientSecret = null, authorizationCode = OC_TOKEN_REQUEST_ACCESS.authorizationCode, redirectUri = OC_TOKEN_REQUEST_ACCESS.redirectUri, codeVerifier = OC_TOKEN_REQUEST_ACCESS.codeVerifier @@ -58,6 +60,8 @@ val OC_REMOTE_TOKEN_REQUEST_PARAMS_REFRESH = TokenRequestParams.RefreshToken( clientAuth = OC_TOKEN_REQUEST_REFRESH.clientAuth, grantType = OC_TOKEN_REQUEST_REFRESH.grantType, scope = OC_TOKEN_REQUEST_REFRESH.scope, + clientId = null, + clientSecret = null, refreshToken = OC_TOKEN_REQUEST_REFRESH.refreshToken ) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/authentication/oauth/model/OIDCServerConfiguration.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/authentication/oauth/model/OIDCServerConfiguration.kt index 5c8de9722ef..1c0d22405ff 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/authentication/oauth/model/OIDCServerConfiguration.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/authentication/oauth/model/OIDCServerConfiguration.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel García de Prada - * Copyright (C) 2020 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -16,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.domain.authentication.oauth.model data class OIDCServerConfiguration( @@ -29,4 +32,7 @@ data class OIDCServerConfiguration( val tokenEndpoint: String, val tokenEndpointAuthMethodsSupported: List?, val userInfoEndpoint: String?, -) +) { + fun isTokenEndpointAuthMethodSupportedClientSecretPost(): Boolean = + tokenEndpointAuthMethodsSupported?.any { it == "client_secret_post" } ?: false +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/authentication/oauth/model/TokenRequest.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/authentication/oauth/model/TokenRequest.kt index 1ac098509d9..a8e814dc3ec 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/authentication/oauth/model/TokenRequest.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/authentication/oauth/model/TokenRequest.kt @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author Abel García de Prada - * Copyright (C) 2020 ownCloud GmbH. + * @author Juan Carlos Garrote Gascón + * + * Copyright (C) 2024 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -16,6 +18,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + package com.owncloud.android.domain.authentication.oauth.model sealed class TokenRequest( @@ -24,24 +27,30 @@ sealed class TokenRequest( val clientAuth: String, val grantType: String, val scope: String, + val clientId: String?, + val clientSecret: String?, ) { class AccessToken( baseUrl: String, tokenEndpoint: String, clientAuth: String, scope: String, + clientId: String? = null, + clientSecret: String? = null, val authorizationCode: String, val redirectUri: String, val codeVerifier: String - ) : TokenRequest(baseUrl, tokenEndpoint, clientAuth, GrantType.ACCESS_TOKEN.string, scope) + ) : TokenRequest(baseUrl, tokenEndpoint, clientAuth, GrantType.ACCESS_TOKEN.string, scope, clientId, clientSecret) class RefreshToken( baseUrl: String, tokenEndpoint: String, clientAuth: String, scope: String, + clientId: String? = null, + clientSecret: String? = null, val refreshToken: String? = null - ) : TokenRequest(baseUrl, tokenEndpoint, clientAuth, GrantType.REFRESH_TOKEN.string, scope) + ) : TokenRequest(baseUrl, tokenEndpoint, clientAuth, GrantType.REFRESH_TOKEN.string, scope, clientId, clientSecret) enum class GrantType(val string: String) { /** Request access token. [More info](https://tools.ietf.org/html/rfc6749#section-4.1.3) */