@@ -23,7 +23,9 @@ import io.opentelemetry.api.trace.Tracer
23
23
import org.slf4j.Logger
24
24
import org.slf4j.LoggerFactory
25
25
26
- enum class IdentityProvider (@JsonValue val alias : String ) {
26
+ enum class IdentityProvider (
27
+ @JsonValue val alias : String ,
28
+ ) {
27
29
MASKINPORTEN (" maskinporten" ),
28
30
AZURE_AD (" azuread" ),
29
31
IDPORTEN (" idporten" ),
@@ -84,43 +86,63 @@ class AuthClient(
84
86
) {
85
87
private val tracer: Tracer = GlobalOpenTelemetry .get().getTracer(" io.nais.common.AuthClient" )
86
88
87
- suspend fun token (target : String ): TokenResponse = try {
88
- tracer.withSpan(" AuthClient/token (${provider.alias} )" , traceAttributes(target)) {
89
- httpClient.submitForm(config.tokenEndpoint, parameters {
90
- set(" target" , target)
91
- set(" identity_provider" , provider.alias)
92
- }).body<TokenResponse .Success >()
89
+ suspend fun token (target : String ): TokenResponse =
90
+ try {
91
+ tracer.withSpan(" AuthClient/token (${provider.alias} )" , traceAttributes(target)) {
92
+ httpClient
93
+ .submitForm(
94
+ config.tokenEndpoint,
95
+ parameters {
96
+ set(" target" , target)
97
+ set(" identity_provider" , provider.alias)
98
+ },
99
+ ).body<TokenResponse .Success >()
100
+ }
101
+ } catch (e: ResponseException ) {
102
+ TokenResponse .Error (e.response.body<TokenErrorResponse >(), e.response.status)
93
103
}
94
- } catch (e: ResponseException ) {
95
- TokenResponse .Error (e.response.body<TokenErrorResponse >(), e.response.status)
96
- }
97
104
98
- suspend fun exchange (target : String , userToken : String ): TokenResponse = try {
99
- tracer.withSpan(" AuthClient/exchange (${provider.alias} )" , traceAttributes(target)) {
100
- httpClient.submitForm(config.tokenExchangeEndpoint, parameters {
101
- set(" target" , target)
102
- set(" user_token" , userToken)
103
- set(" identity_provider" , provider.alias)
104
- }).body<TokenResponse .Success >()
105
+ suspend fun exchange (
106
+ target : String ,
107
+ userToken : String ,
108
+ ): TokenResponse =
109
+ try {
110
+ tracer.withSpan(" AuthClient/exchange (${provider.alias} )" , traceAttributes(target)) {
111
+ httpClient
112
+ .submitForm(
113
+ config.tokenExchangeEndpoint,
114
+ parameters {
115
+ set(" target" , target)
116
+ set(" user_token" , userToken)
117
+ set(" identity_provider" , provider.alias)
118
+ },
119
+ ).body<TokenResponse .Success >()
120
+ }
121
+ } catch (e: ResponseException ) {
122
+ TokenResponse .Error (e.response.body<TokenErrorResponse >(), e.response.status)
105
123
}
106
- } catch (e: ResponseException ) {
107
- TokenResponse .Error (e.response.body<TokenErrorResponse >(), e.response.status)
108
- }
109
124
110
125
suspend fun introspect (accessToken : String ): TokenIntrospectionResponse =
111
126
tracer.withSpan(" AuthClient/introspect (${provider.alias} )" , traceAttributes()) {
112
- httpClient.submitForm(config.tokenIntrospectionEndpoint, parameters {
113
- set(" token" , accessToken)
114
- set(" identity_provider" , provider.alias)
115
- }).body()
127
+ httpClient
128
+ .submitForm(
129
+ config.tokenIntrospectionEndpoint,
130
+ parameters {
131
+ set(" token" , accessToken)
132
+ set(" identity_provider" , provider.alias)
133
+ },
134
+ ).body()
116
135
}
117
136
118
- private fun traceAttributes (target : String? = null) = Attributes .builder().apply {
119
- put(attributeKeyIdentityProvider, provider.alias)
120
- if (target != null ) {
121
- put(attributeKeyTarget, target)
122
- }
123
- }.build()
137
+ private fun traceAttributes (target : String? = null) =
138
+ Attributes
139
+ .builder()
140
+ .apply {
141
+ put(attributeKeyIdentityProvider, provider.alias)
142
+ if (target != null ) {
143
+ put(attributeKeyTarget, target)
144
+ }
145
+ }.build()
124
146
125
147
companion object {
126
148
private val attributeKeyTarget: AttributeKey <String > = AttributeKey .stringKey(" target" )
@@ -134,57 +156,60 @@ class AuthPluginConfiguration(
134
156
var logger : Logger = LoggerFactory .getLogger("io.nais.common.ktor.NaisAuth "),
135
157
)
136
158
137
- val NaisAuth = createRouteScopedPlugin(
138
- name = " NaisAuth" ,
139
- createConfiguration = ::AuthPluginConfiguration ,
140
- ) {
141
- val logger = pluginConfig.logger
142
- val client = pluginConfig.client ? : throw IllegalArgumentException (" NaisAuth plugin: client must be set" )
143
- val ingress = pluginConfig.ingress ? : " "
144
-
145
- val challenge: suspend (ApplicationCall ) -> Unit = { call ->
146
- val target = call.loginUrl(ingress)
147
- logger.info(" unauthenticated: redirecting to '$target '" )
148
- call.respondRedirect(target)
149
- }
150
-
151
- pluginConfig.apply {
152
- onCall { call ->
153
- val token = call.bearerToken()
154
- if (token == null ) {
155
- logger.warn(" unauthenticated: no Bearer token found in Authorization header" )
156
- challenge(call)
157
- return @onCall
158
- }
159
+ val NaisAuth =
160
+ createRouteScopedPlugin(
161
+ name = " NaisAuth" ,
162
+ createConfiguration = ::AuthPluginConfiguration ,
163
+ ) {
164
+ val logger = pluginConfig.logger
165
+ val client = pluginConfig.client ? : throw IllegalArgumentException (" NaisAuth plugin: client must be set" )
166
+ val ingress = pluginConfig.ingress ? : " "
167
+
168
+ val challenge: suspend (ApplicationCall ) -> Unit = { call ->
169
+ val target = call.loginUrl(ingress)
170
+ logger.info(" unauthenticated: redirecting to '$target '" )
171
+ call.respondRedirect(target)
172
+ }
159
173
160
- val introspectResponse = try {
161
- client.introspect(token)
162
- } catch (e: Exception ) {
163
- logger.error(" unauthenticated: introspect request failed: ${e.message} " )
174
+ pluginConfig.apply {
175
+ onCall { call ->
176
+ val token = call.bearerToken()
177
+ if (token == null ) {
178
+ logger.warn(" unauthenticated: no Bearer token found in Authorization header" )
179
+ challenge(call)
180
+ return @onCall
181
+ }
182
+
183
+ val introspectResponse =
184
+ try {
185
+ client.introspect(token)
186
+ } catch (e: Exception ) {
187
+ logger.error(" unauthenticated: introspect request failed: ${e.message} " )
188
+ challenge(call)
189
+ return @onCall
190
+ }
191
+
192
+ if (introspectResponse.active) {
193
+ logger.info(" authenticated - claims='${introspectResponse.other} '" )
194
+ return @onCall
195
+ }
196
+
197
+ logger.warn(" unauthenticated: ${introspectResponse.error} " )
164
198
challenge(call)
165
199
return @onCall
166
200
}
167
-
168
- if (introspectResponse.active) {
169
- logger.info(" authenticated - claims='${introspectResponse.other} '" )
170
- return @onCall
171
- }
172
-
173
- logger.warn(" unauthenticated: ${introspectResponse.error} " )
174
- challenge(call)
175
- return @onCall
176
201
}
177
- }
178
202
179
- logger.info(" NaisAuth plugin loaded." )
180
- }
203
+ logger.info(" NaisAuth plugin loaded." )
204
+ }
181
205
182
206
// loginUrl constructs a URL string that points to the login endpoint (Wonderwall) for redirecting a request.
183
207
// It also indicates that the user should be redirected back to the original request path after authentication
184
208
private fun ApplicationCall.loginUrl (defaultHost : String ): String {
185
- val host = defaultHost.ifEmpty(defaultValue = {
186
- " ${this .request.local.scheme} ://${this .request.host()} "
187
- })
209
+ val host =
210
+ defaultHost.ifEmpty(defaultValue = {
211
+ " ${this .request.local.scheme} ://${this .request.host()} "
212
+ })
188
213
189
214
return " $host /oauth2/login?redirect=${this .request.uri} "
190
215
}
0 commit comments