Skip to content

Commit b2827cc

Browse files
committed
LoginActivity: Extract preferred_username
This is to show a nice username "username" instead of "long-uuid@server" in the web form.
1 parent 97f3b5e commit b2827cc

5 files changed

Lines changed: 46 additions & 3 deletions

File tree

opencloudApp/src/main/java/eu/opencloud/android/presentation/authentication/LoginActivity.kt

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import androidx.core.widget.doAfterTextChanged
4848
import eu.opencloud.android.BuildConfig
4949
import eu.opencloud.android.MainApp.Companion.accountType
5050
import eu.opencloud.android.R
51+
import eu.opencloud.android.data.authentication.KEY_PREFERRED_USERNAME
5152
import eu.opencloud.android.data.authentication.KEY_USER_ID
5253
import eu.opencloud.android.databinding.AccountSetupBinding
5354
import eu.opencloud.android.domain.authentication.oauth.model.ClientRegistrationInfo
@@ -117,6 +118,7 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
117118
private lateinit var serverBaseUrl: String
118119

119120
private var oidcSupported = false
121+
private var preferredUsername: String? = null
120122

121123
private lateinit var binding: AccountSetupBinding
122124

@@ -177,10 +179,12 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
177179
if (loginAction != ACTION_CREATE) {
178180
binding.accountUsername.isEnabled = false
179181
binding.accountUsername.isFocusable = false
180-
userAccount?.name?.let {
181-
username = getUsernameOfAccount(it)
182+
userAccount?.let { account ->
183+
// Prefer preferred_username from id_token (stored in AccountManager) for login_hint,
184+
// fall back to the account name part (which may be a UUID)
185+
username = AccountManager.get(this).getUserData(account, KEY_PREFERRED_USERNAME)
186+
?: getUsernameOfAccount(account.name)
182187
}
183-
184188
}
185189

186190
if (savedInstanceState == null) {
@@ -555,6 +559,12 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
555559
resultBundle = intent.extras
556560
setResult(Activity.RESULT_OK, intent)
557561

562+
// Store preferred_username from id_token for login_hint on re-login
563+
preferredUsername?.let { prefUsername ->
564+
val account = Account(accountName, contextProvider.getString(R.string.account_type))
565+
AccountManager.get(this).setUserData(account, KEY_PREFERRED_USERNAME, prefUsername)
566+
}
567+
558568
authenticationViewModel.discoverAccount(accountName = accountName, discoveryNeeded = loginAction == ACTION_CREATE)
559569
clearAuthState()
560570
}
@@ -777,6 +787,10 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
777787
Timber.d("Tokens received ${uiResult.data}, trying to login, creating account and adding it to account manager")
778788
val tokenResponse = uiResult.data ?: return@observe
779789

790+
// Extract preferred_username from id_token for login_hint on re-login
791+
preferredUsername = extractPreferredUsernameFromIdToken(tokenResponse.idToken)
792+
Timber.d("Preferred username from id_token: $preferredUsername")
793+
780794
// When webfinger provides a client_id without dynamic registration,
781795
// store it so AccountAuthenticator can use it for token refresh
782796
val effectiveClientRegistrationInfo = clientRegistrationInfo
@@ -1071,4 +1085,24 @@ class LoginActivity : AppCompatActivity(), SslUntrustedCertDialog.OnSslUntrusted
10711085
val prefs = getSharedPreferences("auth_state", android.content.Context.MODE_PRIVATE)
10721086
prefs.edit().clear().apply()
10731087
}
1088+
1089+
/**
1090+
* Extract preferred_username from an OIDC id_token JWT.
1091+
* Fallback chain: preferred_username -> email -> sub
1092+
*/
1093+
private fun extractPreferredUsernameFromIdToken(idToken: String?): String? {
1094+
if (idToken == null) return null
1095+
return try {
1096+
val parts = idToken.split(".")
1097+
if (parts.size != 3) return null
1098+
val payload = String(android.util.Base64.decode(parts[1], android.util.Base64.URL_SAFE))
1099+
val json = org.json.JSONObject(payload)
1100+
json.optString("preferred_username").ifBlank { null }
1101+
?: json.optString("email").ifBlank { null }
1102+
?: json.optString("sub").ifBlank { null }
1103+
} catch (e: Exception) {
1104+
Timber.e(e, "Failed to extract preferred_username from id_token")
1105+
null
1106+
}
1107+
}
10741108
}

opencloudComLibrary/src/main/java/eu/opencloud/android/lib/resources/oauth/responses/TokenResponse.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ data class TokenResponse(
4040
@Json(name = "user_id")
4141
val userId: String?,
4242
val scope: String?,
43+
@Json(name = "id_token")
44+
val idToken: String? = null,
4345
@Json(name = "additional_parameters")
4446
val additionalParameters: Map<String, String>?
4547
)

opencloudData/src/main/java/eu/opencloud/android/data/authentication/AuthenticationConstants.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ const val KEY_FEATURE_SPACES = "KEY_FEATURE_SPACES"
4747
/**
4848
* OIDC Client Registration
4949
*/
50+
/**
51+
* Preferred username from OIDC id_token, used for login_hint
52+
*/
53+
const val KEY_PREFERRED_USERNAME = "oc_preferred_username"
54+
5055
const val KEY_CLIENT_REGISTRATION_CLIENT_ID = "client_id"
5156
const val KEY_CLIENT_REGISTRATION_CLIENT_SECRET = "client_secret"
5257
const val KEY_CLIENT_REGISTRATION_CLIENT_EXPIRATION_DATE = "client_secret_expires_at"

opencloudData/src/main/java/eu/opencloud/android/data/oauth/datasources/implementation/OCRemoteOAuthDataSource.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ class OCRemoteOAuthDataSource(
131131
tokenType = this.tokenType,
132132
userId = this.userId,
133133
scope = this.scope,
134+
idToken = this.idToken,
134135
additionalParameters = this.additionalParameters
135136
)
136137

opencloudDomain/src/main/java/eu/opencloud/android/domain/authentication/oauth/model/TokenResponse.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ data class TokenResponse(
2626
val tokenType: String,
2727
val userId: String?,
2828
val scope: String?,
29+
val idToken: String? = null,
2930
val additionalParameters: Map<String, String>?
3031
)

0 commit comments

Comments
 (0)