@@ -50,6 +50,7 @@ import eu.opencloud.android.MainApp.Companion.accountType
5050import eu.opencloud.android.R
5151import eu.opencloud.android.data.authentication.KEY_USER_ID
5252import eu.opencloud.android.databinding.AccountSetupBinding
53+ import eu.opencloud.android.domain.authentication.oauth.model.ClientRegistrationInfo
5354import eu.opencloud.android.domain.authentication.oauth.model.ResponseType
5455import eu.opencloud.android.domain.authentication.oauth.model.TokenRequest
5556import eu.opencloud.android.domain.exceptions.NoNetworkConnectionException
@@ -447,14 +448,28 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
447448 showOrHideBasicAuthFields(shouldBeVisible = false )
448449 authTokenType = OAUTH_TOKEN_TYPE
449450 oidcSupported = true
450- val registrationEndpoint = serverInfo.oidcServerConfiguration.registrationEndpoint
451- if (registrationEndpoint != null ) {
452- registerClient(
451+ val webFingerClientId = serverInfo.webFingerClientId
452+ val webFingerScopes = serverInfo.webFingerScopes
453+ if (webFingerClientId != null ) {
454+ performGetAuthorizationCodeRequest(
453455 authorizationEndpoint = serverInfo.oidcServerConfiguration.authorizationEndpoint.toUri(),
454- registrationEndpoint = registrationEndpoint
456+ clientId = webFingerClientId,
457+ webFingerScopes = webFingerScopes,
455458 )
456459 } else {
457- performGetAuthorizationCodeRequest(serverInfo.oidcServerConfiguration.authorizationEndpoint.toUri())
460+ val registrationEndpoint = serverInfo.oidcServerConfiguration.registrationEndpoint
461+ if (registrationEndpoint != null ) {
462+ registerClient(
463+ authorizationEndpoint = serverInfo.oidcServerConfiguration.authorizationEndpoint.toUri(),
464+ registrationEndpoint = registrationEndpoint,
465+ webFingerScopes = webFingerScopes,
466+ )
467+ } else {
468+ performGetAuthorizationCodeRequest(
469+ authorizationEndpoint = serverInfo.oidcServerConfiguration.authorizationEndpoint.toUri(),
470+ webFingerScopes = webFingerScopes,
471+ )
472+ }
458473 }
459474 }
460475
@@ -559,7 +574,8 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
559574 */
560575 private fun registerClient (
561576 authorizationEndpoint : Uri ,
562- registrationEndpoint : String
577+ registrationEndpoint : String ,
578+ webFingerScopes : List <String >? = null,
563579 ) {
564580 authenticationViewModel.registerClient(registrationEndpoint)
565581 authenticationViewModel.registerClient.observe(this ) {
@@ -570,22 +586,24 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
570586 uiResult.data?.let { clientRegistrationInfo ->
571587 performGetAuthorizationCodeRequest(
572588 authorizationEndpoint = authorizationEndpoint,
573- clientId = clientRegistrationInfo.clientId
589+ clientId = clientRegistrationInfo.clientId,
590+ webFingerScopes = webFingerScopes,
574591 )
575592 }
576593 }
577594
578595 is UIResult .Error -> {
579596 Timber .e(uiResult.error, " Client registration failed." )
580- performGetAuthorizationCodeRequest(authorizationEndpoint)
597+ performGetAuthorizationCodeRequest(authorizationEndpoint, webFingerScopes = webFingerScopes )
581598 }
582599 }
583600 }
584601 }
585602
586603 private fun performGetAuthorizationCodeRequest (
587604 authorizationEndpoint : Uri ,
588- clientId : String = getString(R .string.oauth2_client_id)
605+ clientId : String = getString(R .string.oauth2_client_id),
606+ webFingerScopes : List <String >? = null,
589607 ) {
590608 Timber .d(" A browser should be opened now to authenticate this user." )
591609
@@ -597,19 +615,29 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
597615 // which helps Firefox properly handle the OAuth redirect back to the app
598616 customTabsIntent.intent.addFlags(Intent .FLAG_ACTIVITY_NEW_TASK )
599617
618+ val scope = if (! oidcSupported) {
619+ " "
620+ } else if (webFingerScopes != null ) {
621+ webFingerScopes.joinToString(" " )
622+ } else {
623+ mdmProvider.getBrandingString(CONFIGURATION_OAUTH2_OPEN_ID_SCOPE , R .string.oauth2_openid_scope)
624+ }
625+
600626 val authorizationEndpointUri = OAuthUtils .buildAuthorizationRequest(
601627 authorizationEndpoint = authorizationEndpoint,
602628 redirectUri = OAuthUtils .buildRedirectUri(applicationContext).toString(),
603629 clientId = clientId,
604630 responseType = ResponseType .CODE .string,
605- scope = if (oidcSupported) mdmProvider.getBrandingString( CONFIGURATION_OAUTH2_OPEN_ID_SCOPE , R .string.oauth2_openid_scope) else " " ,
631+ scope = scope ,
606632 prompt = if (oidcSupported) mdmProvider.getBrandingString(CONFIGURATION_OAUTH2_OPEN_ID_PROMPT , R .string.oauth2_openid_prompt) else " " ,
607633 codeChallenge = authenticationViewModel.codeChallenge,
608634 state = authenticationViewModel.oidcState,
609635 username = username,
610636 sendLoginHintAndUser = mdmProvider.getBrandingBoolean(mdmKey = CONFIGURATION_SEND_LOGIN_HINT_AND_USER ,
611637 booleanKey = R .bool.send_login_hint_and_user),
612638 )
639+ Timber .d(" A browser should be opened now to authenticate this user is $authorizationEndpointUri " )
640+
613641
614642 try {
615643 saveAuthState()
@@ -673,6 +701,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
673701 val (clientId, clientSecret) = if (clientRegistrationInfo?.clientId != null && clientRegistrationInfo.clientSecret != null ) {
674702 Pair (clientRegistrationInfo.clientId, clientRegistrationInfo.clientSecret as String )
675703 } else {
704+ // May be overridden below by serverInfo.webFingerClientId if available
676705 Pair (getString(R .string.oauth2_client_id), getString(R .string.oauth2_client_secret))
677706 }
678707
@@ -684,18 +713,21 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
684713 if (serverInfo is ServerInfo .OIDCServer ) {
685714 tokenEndPoint = serverInfo.oidcServerConfiguration.tokenEndpoint
686715
716+ // Use webfinger-provided clientId if available, otherwise keep registration/default
717+ val effectiveClientId = serverInfo.webFingerClientId ? : clientId
718+
687719 // RFC 7636: Public clients (token_endpoint_auth_method: none) must not send Authorization header
688720 if (serverInfo.oidcServerConfiguration.isTokenEndpointAuthMethodNone()) {
689721 clientAuth = " "
690- clientIdForRequest = clientId
722+ clientIdForRequest = effectiveClientId
691723 } else if (serverInfo.oidcServerConfiguration.isTokenEndpointAuthMethodSupportedClientSecretPost()) {
692724 // For client_secret_post, credentials go in body, not Authorization header
693725 clientAuth = " "
694- clientIdForRequest = clientId
726+ clientIdForRequest = effectiveClientId
695727 clientSecretForRequest = clientSecret
696728 } else {
697729 // For other methods (e.g., client_secret_basic), use Basic auth header
698- clientAuth = OAuthUtils .getClientAuth(clientSecret, clientId )
730+ clientAuth = OAuthUtils .getClientAuth(clientSecret, effectiveClientId )
699731 }
700732 } else {
701733 tokenEndPoint = " $serverBaseUrl${File .separator}${contextProvider.getString(R .string.oauth2_url_endpoint_access)} "
@@ -725,18 +757,41 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
725757 Timber .d(" Tokens received ${uiResult.data} , trying to login, creating account and adding it to account manager" )
726758 val tokenResponse = uiResult.data ? : return @observe
727759
760+ // When webfinger provides a client_id without dynamic registration,
761+ // store it so AccountAuthenticator can use it for token refresh
762+ val effectiveClientRegistrationInfo = clientRegistrationInfo
763+ ? : (serverInfo as ? ServerInfo .OIDCServer )?.webFingerClientId?.let { wfClientId ->
764+ ClientRegistrationInfo (
765+ clientId = wfClientId,
766+ clientSecret = null ,
767+ clientIdIssuedAt = null ,
768+ clientSecretExpiration = 0 ,
769+ )
770+ }
771+
772+ // Scope priority: webfinger scopes > MDM/string-resource > token response
773+ val webFingerScopes = if (serverInfo is ServerInfo .OIDCServer ) {
774+ serverInfo.webFingerScopes
775+ } else {
776+ null
777+ }
778+ val effectiveScope = if (! oidcSupported) {
779+ tokenResponse.scope
780+ } else if (webFingerScopes != null ) {
781+ webFingerScopes.joinToString(" " )
782+ } else {
783+ mdmProvider.getBrandingString(CONFIGURATION_OAUTH2_OPEN_ID_SCOPE , R .string.oauth2_openid_scope)
784+ }
785+
728786 authenticationViewModel.loginOAuth(
729787 serverBaseUrl = serverBaseUrl,
730788 username = tokenResponse.additionalParameters?.get(KEY_USER_ID ).orEmpty(),
731789 authTokenType = OAUTH_TOKEN_TYPE ,
732790 accessToken = tokenResponse.accessToken,
733791 refreshToken = tokenResponse.refreshToken.orEmpty(),
734- scope = if (oidcSupported) mdmProvider.getBrandingString(
735- CONFIGURATION_OAUTH2_OPEN_ID_SCOPE ,
736- R .string.oauth2_openid_scope,
737- ) else tokenResponse.scope,
792+ scope = effectiveScope,
738793 updateAccountWithUsername = if (loginAction != ACTION_CREATE ) userAccount?.name else null ,
739- clientRegistrationInfo = clientRegistrationInfo
794+ clientRegistrationInfo = effectiveClientRegistrationInfo
740795 )
741796 }
742797
0 commit comments