Skip to content

Commit

Permalink
går over til wonderwall
Browse files Browse the repository at this point in the history
  • Loading branch information
davidsteinsland committed Dec 27, 2023
1 parent 7159ea4 commit abbc992
Show file tree
Hide file tree
Showing 16 changed files with 163 additions and 331 deletions.
2 changes: 1 addition & 1 deletion backend/src/main/kotlin/no/nav/spanner/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fun main() {
.info("Spanner startet")

val adConfig = AzureADConfig.fromEnv(configProps)
val spleis = if (spannerConfig.development) LokaleKjenninger else Spleis.from(spannerConfig, AzureAD(adConfig))
val spleis = Spleis.from(spannerConfig, AzureAD(adConfig))

embeddedServer(CIO, environment = applicationEngineEnvironment {
log = LoggerFactory.getLogger("Spanner")
Expand Down
10 changes: 8 additions & 2 deletions backend/src/main/kotlin/no/nav/spanner/AuditLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import io.ktor.server.application.*
import io.ktor.http.HttpMethod.Companion.Get
import io.ktor.http.HttpMethod.Companion.Post
import io.ktor.http.HttpMethod.Companion.Put
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.request.*
import io.ktor.util.pipeline.*
import no.nav.spanner.AuditLogger.Operasjon.*
import org.slf4j.LoggerFactory
import java.time.ZonedDateTime
Expand Down Expand Up @@ -45,8 +48,11 @@ internal class AuditLogger(private val brukerIdent: String) {

companion object {
private val logger = LoggerFactory.getLogger("auditLogger")
fun SpannerSession.audit(): AuditLogger {
val ident = requireNotNull(idToken.asJwt().getClaim("NAVident").asString()) { "NAVident mangler i tokenet" }
fun PipelineContext<Unit, ApplicationCall>.audit() =
call.principal<JWTPrincipal>()?.audit()?.log(call)

fun JWTPrincipal.audit(): AuditLogger {
val ident = requireNotNull(this["NAVident"]) { "NAVident mangler i tokenet" }
return AuditLogger(ident)
}
}
Expand Down
47 changes: 25 additions & 22 deletions backend/src/main/kotlin/no/nav/spanner/AzureADConfig.kt
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
package no.nav.spanner

import com.auth0.jwk.JwkProvider
import com.auth0.jwk.JwkProviderBuilder
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.natpryce.konfig.Configuration
import com.natpryce.konfig.Key
import com.natpryce.konfig.stringType
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URI
import java.net.URL

class AzureADConfig(
val discoveryUrl: String,
private val jwkProvider: JwkProvider,
private val issuer: String,
val tokenEndpoint: String,
val clientId: String,
val clientSecret: String,
authorizationUrl: String? = null
val clientSecret: String
) {
private val discovered = discoveryUrl.discover()
val tokenEndpoint = discovered["token_endpoint"]?.textValue()
?: throw RuntimeException("Unable to discover token endpoint")
val authorizationEndpoint = authorizationUrl ?: discovered["authorization_endpoint"]?.textValue()
?: throw RuntimeException("Unable to discover authorization endpoint")
fun konfigurerJwtAuth(config: AuthenticationConfig) {
config.jwt {
verifier(jwkProvider, issuer) {
withAudience(clientId)
withClaimPresence("NAVident")
withClaimPresence("preferred_username")
withClaimPresence("name")
}
validate { credentials ->
JWTPrincipal(credentials.payload)
}
}
}

companion object {
fun fromEnv(config: Configuration) = AzureADConfig(
discoveryUrl = config[Key("AZURE_APP_WELL_KNOWN_URL", stringType)],
jwkProvider = JwkProviderBuilder(URI(config[Key("AZURE_APP_WELL_KNOWN_URL", stringType)]).toURL()).build(),
issuer = config[Key("AZURE_OPENID_CONFIG_ISSUER", stringType)],
tokenEndpoint = config[Key("AZURE_OPENID_CONFIG_TOKEN_ENDPOINT", stringType)],
clientId = config[Key("AZURE_APP_CLIENT_ID", stringType)],
clientSecret = config[Key("AZURE_APP_CLIENT_SECRET", stringType)],
authorizationUrl = config.getOrNull(Key("AUTHORIZATION_URL", stringType)),
)
}
}

private fun String.discover(): JsonNode {
val (responseCode, responseBody) = this.fetchUrl()
if (responseCode >= 300 || responseBody == null) throw IOException("got status $responseCode from ${this}.")
return jacksonObjectMapper().readTree(responseBody)
}

private fun String.fetchUrl() = with(URL(this).openConnection() as HttpURLConnection) {
requestMethod = "GET"
val stream: InputStream? = if (responseCode < 300) this.inputStream else this.errorStream
responseCode to stream?.bufferedReader()?.readText()
}
30 changes: 0 additions & 30 deletions backend/src/main/kotlin/no/nav/spanner/AzureAd.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.apache.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.jackson.*
import io.ktor.util.*
Expand Down Expand Up @@ -46,34 +44,6 @@ class AzureAD(private val config: AzureADConfig) {
?.asText()!!
}

@OptIn(InternalAPI::class)
internal suspend fun refreshToken(refreshToken: String): SpannerSession {
val requestBody = listOf(
"tenant" to "nav.no",
"client_id" to config.clientId,
"grant_type" to "refresh_token",
"scope" to "openid offline_access ${config.clientId}/.default",
"refresh_token" to refreshToken,
"client_secret" to config.clientSecret,
).formUrlEncode()
val response = httpClient.post(config.tokenEndpoint) {
accept(ContentType.Application.Json)
contentType(ContentType.Application.FormUrlEncoded)
body = requestBody
}
Log.logger(AzureAD::class.java)
.response(response)
.info("Refreshing session")

val jsonRespons = response.body<JsonNode>()
return SpannerSession(
accessToken = jsonRespons.path("access_token").asText()!!,
refreshToken = jsonRespons.path("refresh_token").asText()!!,
idToken = jsonRespons.path("id_token").asText()!!,
expiresIn = jsonRespons.path("expires_in").asLong()
)
}

private fun createOnBehalfOfRequestBody(clientId: String, list: List<Pair<String, String>>) =
createTokenRequestBody(list, clientId)

Expand Down
12 changes: 2 additions & 10 deletions backend/src/main/kotlin/no/nav/spanner/FrontendRouting.kt
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
package no.nav.spanner

import io.ktor.server.application.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

internal fun Route.frontendRouting() {
get("/") {
call.respondText(
this::class.java.classLoader.getResource("static/index.html")!!.readText(),
ContentType.Text.Html
)
}
get("/person/*") {
call.respondText(
this::class.java.classLoader.getResource("static/index.html")!!.readText(),
ContentType.Text.Html
)
}
static("/") {
resources("static/")
}
staticResources("/", "/static/")
}
46 changes: 0 additions & 46 deletions backend/src/main/kotlin/no/nav/spanner/OidcRouting.kt

This file was deleted.

Loading

0 comments on commit abbc992

Please sign in to comment.