/
Auth.kt
99 lines (79 loc) · 3.57 KB
/
Auth.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/*
* Copyright 2014-2019 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
package io.ktor.client.plugins.auth
import io.ktor.client.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.http.auth.*
import io.ktor.util.*
/**
* A client's plugin that handles authentication and authorization.
* Typical usage scenarios include logging in users and gaining access to specific resources.
*
* You can learn more from [Authentication and authorization](https://ktor.io/docs/auth.html).
*
* [providers] - list of auth providers to use.
*/
@KtorDsl
public class Auth private constructor(
public val providers: MutableList<AuthProvider> = mutableListOf()
) {
public companion object Plugin : HttpClientPlugin<Auth, Auth> {
/**
* Shows that request should skip auth and refresh token procedure.
*/
public val AuthCircuitBreaker: AttributeKey<Unit> = AttributeKey("auth-request")
override val key: AttributeKey<Auth> = AttributeKey("DigestAuth")
override fun prepare(block: Auth.() -> Unit): Auth {
return Auth().apply(block)
}
@OptIn(InternalAPI::class)
override fun install(plugin: Auth, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.State) {
plugin.providers.filter { it.sendWithoutRequest(context) }.forEach {
it.addRequestHeaders(context)
}
}
scope.plugin(HttpSend).intercept { context ->
val origin = execute(context)
if (origin.response.status != HttpStatusCode.Unauthorized) return@intercept origin
if (origin.request.attributes.contains(AuthCircuitBreaker)) return@intercept origin
var call = origin
val candidateProviders = HashSet(plugin.providers)
while (call.response.status == HttpStatusCode.Unauthorized) {
val headerValues = call.response.headers.getAll(HttpHeaders.WWWAuthenticate)
val authHeaders = headerValues?.map { parseAuthorizationHeaders(it) }?.flatten() ?: emptyList()
var providerOrNull: AuthProvider? = null
var authHeader: HttpAuthHeader? = null
when {
authHeaders.isEmpty() && candidateProviders.size == 1 -> {
providerOrNull = candidateProviders.first()
}
authHeaders.isEmpty() -> return@intercept call
else -> authHeader = authHeaders.find { header ->
providerOrNull = candidateProviders.find { it.isApplicable(header) }
providerOrNull != null
}
}
val provider = providerOrNull ?: return@intercept call
if (!provider.refreshToken(call.response)) return@intercept call
candidateProviders.remove(provider)
val request = HttpRequestBuilder()
request.takeFromWithExecutionContext(context)
provider.addRequestHeaders(request, authHeader)
request.attributes.put(AuthCircuitBreaker, Unit)
call = execute(request)
}
return@intercept call
}
}
}
}
/**
* Install [Auth] plugin.
*/
public fun HttpClientConfig<*>.Auth(block: Auth.() -> Unit) {
install(Auth, block)
}