1
+ package no.nav.dagpenger.ktor.auth
2
+
3
+ import io.ktor.application.ApplicationCall
4
+ import io.ktor.application.call
5
+ import io.ktor.auth.Authentication
6
+ import io.ktor.auth.AuthenticationFailedCause
7
+ import io.ktor.auth.AuthenticationPipeline
8
+ import io.ktor.auth.AuthenticationProvider
9
+ import io.ktor.auth.Credential
10
+ import io.ktor.auth.Principal
11
+ import io.ktor.auth.UnauthorizedResponse
12
+ import io.ktor.http.auth.HeaderValueEncoding
13
+ import io.ktor.http.auth.HttpAuthHeader
14
+ import io.ktor.request.ApplicationRequest
15
+ import io.ktor.response.respond
16
+
17
+ enum class ApiKeyLocation (val location : String ) {
18
+ QUERY (" query" ),
19
+ HEADER (" header" )
20
+ }
21
+
22
+ data class ApiKeyCredential (val value : String ) : Credential
23
+ data class ApiPrincipal (val apiKeyCredential : ApiKeyCredential ? ) : Principal
24
+
25
+ /* *
26
+ * Represents a Api Key authentication provider
27
+ * @param name is the name of the provider, or `null` for a default provider
28
+ */
29
+
30
+ class ApiKeyAuthenticationProvider internal constructor(config : Configuration ) : AuthenticationProvider(config) {
31
+
32
+ internal var apiKeyName: String = config.apiKeyName
33
+ internal var apiKeyLocation: ApiKeyLocation = config.apiKeyLocation
34
+ internal val authenticationFunction = config.authenticationFunction
35
+
36
+ class Configuration (name : String? ) : AuthenticationProvider.Configuration(name) {
37
+ internal var authenticationFunction: suspend ApplicationCall .(ApiKeyCredential ) -> Principal ? = { null }
38
+
39
+ var apiKeyName: String = " "
40
+
41
+ var apiKeyLocation: ApiKeyLocation = ApiKeyLocation .HEADER
42
+
43
+ fun validate (body : suspend ApplicationCall .(ApiKeyCredential ) -> Principal ? ) {
44
+ authenticationFunction = body
45
+ }
46
+
47
+ internal fun build () = ApiKeyAuthenticationProvider (this )
48
+ }
49
+ }
50
+
51
+ fun Authentication.Configuration.apiKeyAuth (
52
+ name : String? = null,
53
+ configure : ApiKeyAuthenticationProvider .Configuration .() -> Unit
54
+ ) {
55
+ val provider = ApiKeyAuthenticationProvider .Configuration (name).apply (configure).build()
56
+ val apiKeyName = provider.apiKeyName
57
+ val apiKeyLocation = provider.apiKeyLocation
58
+ val authenticate = provider.authenticationFunction
59
+
60
+ provider.pipeline.intercept(AuthenticationPipeline .RequestAuthentication ) { context ->
61
+ val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
62
+ val principal = credentials?.let { authenticate(call, it) }
63
+
64
+ val cause = when {
65
+ credentials == null -> AuthenticationFailedCause .NoCredentials
66
+ principal == null -> AuthenticationFailedCause .InvalidCredentials
67
+ else -> null
68
+ }
69
+
70
+ if (cause != null ) {
71
+ context.challenge(apiKeyName, cause) {
72
+ call.respond(
73
+ UnauthorizedResponse (
74
+ HttpAuthHeader .Parameterized (
75
+ " API_KEY" ,
76
+ mapOf (" key" to apiKeyName),
77
+ HeaderValueEncoding .QUOTED_ALWAYS
78
+ )
79
+ )
80
+ )
81
+ it.complete()
82
+ }
83
+ }
84
+
85
+ if (principal != null ) {
86
+ context.principal(principal)
87
+ }
88
+ }
89
+
90
+ register(provider)
91
+ }
92
+
93
+ fun ApplicationRequest.apiKeyAuthenticationCredentials (
94
+ apiKeyName : String ,
95
+ apiKeyLocation : ApiKeyLocation
96
+ ): ApiKeyCredential ? {
97
+ return when (val value: String? = when (apiKeyLocation) {
98
+ ApiKeyLocation .QUERY -> this .queryParameters[apiKeyName]
99
+ ApiKeyLocation .HEADER -> this .headers[apiKeyName]
100
+ }) {
101
+ null -> null
102
+ else -> ApiKeyCredential (value)
103
+ }
104
+ }
0 commit comments