Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions solr/ui/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<!-- Descriptions (desc) -->
<string name="desc_to_get_started">To get started, please provide a Solr host URL:</string>
<string name="desc_solr_instance_with_auth">The Solr instance %1$s has enabled authentication.</string>
<string name="desc_sign_in_with_credentials">To authenticate you may use your credentials for Basic authentication.</string>
<string name="desc_sign_in_with_credentials_to_realm">To authenticate you may use your credentials for the realm %1$s.</string>

<!-- Errors (error) -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface BasicAuthComponent {
* does not distinguish input fields.
*/
data class Model(
val realm: String = "",
val realm: String? = null,
val username: String = "",
val password: String = "",
val hasError: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,11 @@ class DefaultAuthenticationComponent(
}

init {
lifecycle.doOnCreate {
methods.forEach { method ->
when (method) {
is AuthMethod.BasicAuthMethod ->
basicAuthNavigation.activate(configuration = BasicAuthConfiguration(method))
}
methods.forEach { method ->
when (method) {
is AuthMethod.BasicAuthMethod ->
basicAuthNavigation.activate(configuration = BasicAuthConfiguration(method))
is AuthMethod.Unknown -> {} // TODO Handle unknown auth methods
}
}
}
Expand All @@ -120,9 +119,9 @@ class DefaultAuthenticationComponent(
OnAuthenticated(
option = AuthOption.BasicAuthOption(
url = url,
method = output.method,
username = output.username,
password = output.password,
realm = output.method.realm,
),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class HttpStartStoreClient(
methods = methods,
message =
if (methods.isEmpty()) {
"Unauthorized response received with missing or unsupported auth method."
"Unauthorized response received with missing auth methods."
} else {
null
},
Expand All @@ -83,15 +83,38 @@ class HttpStartStoreClient(
* @param headers The headers to use for extracting the information.
*/
private fun getAuthMethodsFromHeader(headers: Headers): List<AuthMethod> {
val authHeader = headers["Www-Authenticate"]
val parts = authHeader?.split(" ", limit = 2)
val scheme = parts?.firstOrNull()
// Note that on JVM headers.getAll() may return multiple values, whereas in WebAssembly/JS
// it may merge multiple headers and separate them by comma
val authHeaders = headers.getAll("Www-Authenticate") ?: emptyList()

// TODO Get realm from header value
return authHeaders
// Split by comma, as there is the chance that headers will be merged and separated by
// comma (e.g. on web target)
.flatMap { header -> header.split(",") }
.map { authHeader ->
val (scheme, params) = parseWwwAuthenticate(authHeader)

return when (scheme) {
"Basic" -> listOf(AuthMethod.BasicAuthMethod(realm = "solr"))
else -> emptyList()
}
when (scheme.lowercase()) {
"basic", "xbasic" -> AuthMethod.BasicAuthMethod(realm = params["realm"])
else -> AuthMethod.Unknown
}
}
}

private fun parseWwwAuthenticate(headerValue: String): Pair<String, Map<String, String>> {
val parts = headerValue.split(" ", limit = 2)
val scheme = parts[0]

// The below mapping is not supporting commas or spaces in the parameter values, nor
// parameters separated by commas (only spaces)
val params = parts.getOrNull(1)
?.split(" ")
?.map(String::trim)
?.associate {
val (k, v) = it.split("=", limit = 2)
k to v.trim('"')
}
?: emptyMap()
return scheme to params
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,7 @@ sealed interface AuthMethod {
* @property realm The realm of the basic auth.
*/
@Serializable
data class BasicAuthMethod(val realm: String = "") : AuthMethod
data class BasicAuthMethod(val realm: String? = null) : AuthMethod

data object Unknown : AuthMethod
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ sealed interface AuthOption {
/**
* Authentication option for authenticating with basic auth (credentials).
*
* @property method The basic auth method used for authentication (holds metadata).
* @property url The URL of the instance that requires basic auth and the credentials of this
* instance are for.
* @property username The username to use for further authenticated requests.
* @property password The password to use for further authenticated requests.
* @property realm The realm defined and used in the Basic auth method.
*/
@Serializable
data class BasicAuthOption(
val method: AuthMethod.BasicAuthMethod,
val url: Url,
val username: String,
val password: String,
val realm: String? = null,
) : AuthOption
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,25 @@ fun getHttpClientWithAuthOption(option: AuthOption) = when (option) {
is AuthOption.None -> getDefaultClient(option.url)
is AuthOption.BasicAuthOption -> getHttpClientWithCredentials(
url = option.url,
realm = option.realm,
username = option.username,
password = option.password,
)
}

fun getHttpClientWithCredentials(
url: Url = Url("http://127.0.0.1:8983/"),
username: String,
password: String,
url: Url = Url("http://127.0.0.1:8983/"),
realm: String? = null,
) = getDefaultClient(url) {
install(Auth) {
basic {
credentials { BasicAuthCredentials(username, password) }
// Always include the credentials, because we are accessing protected endpoints from a
// not protected asset (web-assembly app)
sendWithoutRequest { true }
this.realm = realm
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import org.apache.solr.ui.components.auth.BasicAuthComponent
import org.apache.solr.ui.generated.resources.Res
import org.apache.solr.ui.generated.resources.action_sign_in_with_credentials
import org.apache.solr.ui.generated.resources.authenticating
import org.apache.solr.ui.generated.resources.desc_sign_in_with_credentials
import org.apache.solr.ui.generated.resources.desc_sign_in_with_credentials_to_realm
import org.apache.solr.ui.generated.resources.label_password
import org.apache.solr.ui.generated.resources.label_username
Expand Down Expand Up @@ -62,7 +63,9 @@ fun BasicAuthContent(
val model by component.model.collectAsState()

Text(
text = stringResource(Res.string.desc_sign_in_with_credentials_to_realm, model.realm),
text = model.realm?.let {
stringResource(Res.string.desc_sign_in_with_credentials_to_realm, it)
} ?: stringResource(Res.string.desc_sign_in_with_credentials),
style = MaterialTheme.typography.bodyMedium,
)

Expand Down
2 changes: 1 addition & 1 deletion solr/webapp/web/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
-->
<init-param>
<param-name>excludePatterns</param-name>
<param-value>/partials/.+,/libs/.+,/css/.+,/js/.+,/img/.+,/templates/.+</param-value>
<param-value>/partials/.+,/libs/.+,/css/.+,/js/.+,/img/.+,/templates/.+,/ui/.*</param-value>
</init-param>
</filter>

Expand Down
Loading