-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
094d38d
commit 33d6f7a
Showing
23 changed files
with
625 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Sikkerhetsmodul | ||
|
||
Felles sikkerhetsmodul for autentisering og autorisering. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
plugins { | ||
kotlin("jvm") | ||
} | ||
|
||
dependencies { | ||
implementation(project(":lib:error-handling")) | ||
implementation(libs.ktor.server.auth) | ||
implementation(libs.logbackClassic) | ||
implementation(libs.nav.security.tokenValidationKtorV2) | ||
|
||
//Test | ||
testImplementation(project(":lib:pdl-client")) | ||
testImplementation(libs.nav.poao.tilgangClient) | ||
testImplementation(libs.nav.security.mockOauth2Server) | ||
testImplementation(libs.bundles.testLibsWithUnitTesting) | ||
testImplementation(libs.ktor.server.testJvm) | ||
testImplementation(libs.ktor.client.contentNegotiation) | ||
testImplementation(libs.ktor.serialization.jackson) | ||
testImplementation(libs.jackson.datatypeJsr310) | ||
} | ||
|
||
tasks.withType<Test>().configureEach { | ||
useJUnitPlatform() | ||
} |
6 changes: 6 additions & 0 deletions
6
...c/main/kotlin/no/nav/paw/security/authentication/exception/BearerTokenManglerException.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package no.nav.paw.security.authentication.exception | ||
|
||
import no.nav.paw.error.exception.AuthenticationException | ||
|
||
class BearerTokenManglerException(message: String) : | ||
AuthenticationException("PAW_BEARER_TOKEN_MANGLER", message) |
6 changes: 6 additions & 0 deletions
6
...ity/src/main/kotlin/no/nav/paw/security/authentication/exception/IngenTilgangException.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package no.nav.paw.security.authentication.exception | ||
|
||
import no.nav.paw.error.exception.AuthorizationException | ||
|
||
class IngenTilgangException(message: String) : | ||
AuthorizationException("PAW_INGEN_TILGANG", message) |
6 changes: 6 additions & 0 deletions
6
...c/main/kotlin/no/nav/paw/security/authentication/exception/UgyldigBearerTokenException.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package no.nav.paw.security.authentication.exception | ||
|
||
import no.nav.paw.error.exception.AuthorizationException | ||
|
||
class UgyldigBearerTokenException(message: String) : | ||
AuthorizationException("PAW_UGYLDIG_BEARER_TOKEN", message) |
6 changes: 6 additions & 0 deletions
6
...ty/src/main/kotlin/no/nav/paw/security/authentication/exception/UgyldigBrukerException.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package no.nav.paw.security.authentication.exception | ||
|
||
import no.nav.paw.error.exception.AuthorizationException | ||
|
||
class UgyldigBrukerException(message: String) : | ||
AuthorizationException("PAW_UGYLDIG_BRUKER", message) |
11 changes: 11 additions & 0 deletions
11
lib/security/src/main/kotlin/no/nav/paw/security/authentication/model/Bruker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package no.nav.paw.security.authentication.model | ||
|
||
import java.util.* | ||
|
||
sealed class Bruker<ID : Any>( | ||
open val ident: ID | ||
) | ||
|
||
data class Sluttbruker(override val ident: Identitetsnummer) : Bruker<Identitetsnummer>(ident) | ||
data class NavAnsatt(val oid: UUID, override val ident: String) : Bruker<String>(ident) | ||
data class M2MToken(val oid: UUID) : Bruker<String>("N/A") |
10 changes: 10 additions & 0 deletions
10
lib/security/src/main/kotlin/no/nav/paw/security/authentication/model/Identitetsnummer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package no.nav.paw.security.authentication.model | ||
|
||
@JvmInline | ||
value class Identitetsnummer(val verdi: String) { | ||
override fun toString(): String { | ||
return "*".repeat(verdi.length) | ||
} | ||
} | ||
|
||
fun String.asIdentitetsnummer(): Identitetsnummer = Identitetsnummer(this) |
95 changes: 95 additions & 0 deletions
95
lib/security/src/main/kotlin/no/nav/paw/security/authentication/token/AccessToken.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package no.nav.paw.security.authentication.token | ||
|
||
import no.nav.paw.security.authentication.exception.UgyldigBearerTokenException | ||
import no.nav.paw.security.authentication.model.Identitetsnummer | ||
import no.nav.security.token.support.core.context.TokenValidationContext | ||
import no.nav.security.token.support.core.jwt.JwtToken | ||
import java.util.* | ||
|
||
data class AccessToken( | ||
val jwt: String, | ||
val issuer: Issuer, | ||
val claims: Claims | ||
) { | ||
fun isM2MToken(): Boolean { | ||
return claims.getOrNull(Roles)?.contains("access_as_application") ?: false | ||
} | ||
} | ||
|
||
sealed class Issuer(val name: String) | ||
|
||
data object IdPorten : Issuer("idporten") | ||
data object TokenX : Issuer("tokenx") | ||
data object AzureAd : Issuer("azure") | ||
|
||
class Claims(private val claims: Map<Claim<*>, Any>) { | ||
@Suppress("UNCHECKED_CAST") | ||
fun <T : Any> getOrNull(claim: Claim<T>): T? = claims[claim] as T? | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
fun <T : Any> getOrThrow(claim: Claim<T>): T = claims[claim] as T? | ||
?: throw UgyldigBearerTokenException("Bearer Token mangler påkrevd claim ${claim.name}") | ||
|
||
fun isEmpty(): Boolean = claims.isEmpty() | ||
|
||
fun contains(claim: Claim<*>): Boolean = claims.containsKey(claim) | ||
} | ||
|
||
abstract class Claim<A : Any>( | ||
open val name: String, | ||
open val resolve: (Any) -> A | ||
) | ||
|
||
sealed class SingleClaim<A : Any>( | ||
override val name: String, | ||
override val resolve: (Any) -> A | ||
) : Claim<A>(name, resolve) | ||
|
||
sealed class ListClaim<A : Any>( | ||
override val name: String, | ||
override val resolve: (Any) -> List<A> | ||
) : Claim<List<A>>(name, resolve) | ||
|
||
data object PID : SingleClaim<Identitetsnummer>("pid", { Identitetsnummer(it.toString()) }) | ||
data object OID : SingleClaim<UUID>("oid", { UUID.fromString(it.toString()) }) | ||
data object Name : SingleClaim<String>("name", { it.toString() }) | ||
data object NavIdent : SingleClaim<String>("NAVident", { it.toString() }) | ||
data object Roles : ListClaim<String>("roles", { value -> (value as List<*>).map { it.toString() } }) | ||
|
||
sealed class ResolveToken(val issuer: Issuer, val claims: List<Claim<*>>) | ||
|
||
data object IdPortenToken : ResolveToken(IdPorten, listOf(PID)) | ||
data object TokenXToken : ResolveToken(TokenX, listOf(PID)) | ||
data object AzureAdToken : ResolveToken(AzureAd, listOf(OID, Name, NavIdent, Roles)) | ||
|
||
private val validTokens: List<ResolveToken> = listOf(IdPortenToken, TokenXToken, AzureAdToken) | ||
|
||
fun TokenValidationContext.resolveTokens(): List<AccessToken> { | ||
return validTokens | ||
.mapNotNull { resolveToken -> | ||
getJwtToken(resolveToken.issuer.name)?.let { resolveToken to it } | ||
} | ||
.map { (resolveToken, jwtToken) -> | ||
val claims = jwtToken.resolveClaims(resolveToken) | ||
AccessToken(jwtToken.encodedToken, resolveToken.issuer, claims) | ||
} | ||
} | ||
|
||
private fun JwtToken.resolveClaims(resolveToken: ResolveToken): Claims { | ||
val claims = resolveToken.claims | ||
.mapNotNull { claim -> | ||
when (claim) { | ||
is ListClaim<*> -> { | ||
val value = jwtTokenClaims.getAsList(claim.name) | ||
value?.let { claim to claim.resolve(value) } | ||
} | ||
|
||
else -> { | ||
val value = jwtTokenClaims.getStringClaim(claim.name) | ||
value?.let { claim to claim.resolve(value) } | ||
} | ||
} | ||
} | ||
.toMap() | ||
return Claims(claims) | ||
} |
6 changes: 6 additions & 0 deletions
6
...ecurity/src/main/kotlin/no/nav/paw/security/authorization/context/AuthorizationContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package no.nav.paw.security.authorization.context | ||
|
||
data class AuthorizationContext( | ||
val requestContext: RequestContext, | ||
val securityContext: SecurityContext | ||
) |
48 changes: 48 additions & 0 deletions
48
lib/security/src/main/kotlin/no/nav/paw/security/authorization/context/RequestContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package no.nav.paw.security.authorization.context | ||
|
||
import io.ktor.http.HttpHeaders | ||
import io.ktor.server.application.ApplicationCall | ||
import io.ktor.server.application.call | ||
import io.ktor.server.auth.principal | ||
import io.ktor.server.request.ApplicationRequest | ||
import io.ktor.util.pipeline.PipelineContext | ||
import no.nav.security.token.support.v2.TokenValidationContextPrincipal | ||
|
||
sealed class NavHeader(val name: String) | ||
|
||
data object TraceParent : NavHeader("traceparent") | ||
data object NavCallId : NavHeader("Nav-Call-Id") | ||
data object NavConsumerId : NavHeader("Nav-Consumer-Id") | ||
data object NavIdent : NavHeader("Nav-Ident") | ||
|
||
data class RequestHeaders( | ||
val authorization: String?, | ||
val navCallId: String?, | ||
val traceParent: String?, | ||
val navConsumerId: String?, | ||
val navIdent: String?, | ||
) | ||
|
||
data class RequestContext( | ||
val request: ApplicationRequest, | ||
val headers: RequestHeaders, | ||
val principal: TokenValidationContextPrincipal? | ||
) | ||
|
||
fun PipelineContext<Unit, ApplicationCall>.resolveRequestContext(): RequestContext { | ||
return RequestContext( | ||
request = call.request, | ||
headers = resolveRequestHeaders(), | ||
principal = call.principal<TokenValidationContextPrincipal>() | ||
) | ||
} | ||
|
||
fun PipelineContext<Unit, ApplicationCall>.resolveRequestHeaders(): RequestHeaders { | ||
return RequestHeaders( | ||
authorization = call.request.headers[HttpHeaders.Authorization], | ||
traceParent = call.request.headers[TraceParent.name], | ||
navCallId = call.request.headers[NavCallId.name], | ||
navConsumerId = call.request.headers[NavConsumerId.name], | ||
navIdent = call.request.headers[NavIdent.name], | ||
) | ||
} |
53 changes: 53 additions & 0 deletions
53
lib/security/src/main/kotlin/no/nav/paw/security/authorization/context/SecurityContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package no.nav.paw.security.authorization.context | ||
|
||
import no.nav.paw.security.authentication.exception.BearerTokenManglerException | ||
import no.nav.paw.security.authentication.exception.UgyldigBearerTokenException | ||
import no.nav.paw.security.authentication.model.Bruker | ||
import no.nav.paw.security.authentication.model.M2MToken | ||
import no.nav.paw.security.authentication.model.NavAnsatt | ||
import no.nav.paw.security.authentication.model.Sluttbruker | ||
import no.nav.paw.security.authentication.token.AccessToken | ||
import no.nav.paw.security.authentication.token.AzureAd | ||
import no.nav.paw.security.authentication.token.IdPorten | ||
import no.nav.paw.security.authentication.token.NavIdent | ||
import no.nav.paw.security.authentication.token.OID | ||
import no.nav.paw.security.authentication.token.PID | ||
import no.nav.paw.security.authentication.token.TokenX | ||
import no.nav.paw.security.authentication.token.resolveTokens | ||
|
||
data class SecurityContext( | ||
val bruker: Bruker<*>, | ||
val accessToken: AccessToken | ||
) | ||
|
||
fun RequestContext.resolveSecurityContext(): SecurityContext { | ||
val tokenContext = principal?.context ?: throw BearerTokenManglerException("Bearer Token mangler") | ||
|
||
val accessToken = tokenContext.resolveTokens().firstOrNull() // Kan støtte flere tokens | ||
?: throw UgyldigBearerTokenException("Ingen gyldige Bearer Tokens funnet") | ||
|
||
if (accessToken.claims.isEmpty()) { | ||
throw UgyldigBearerTokenException("Bearer Token mangler påkrevd innhold") | ||
} | ||
|
||
val bruker = when (accessToken.issuer) { | ||
is IdPorten -> Sluttbruker(accessToken.claims.getOrThrow(PID)) | ||
is TokenX -> Sluttbruker(accessToken.claims.getOrThrow(PID)) | ||
is AzureAd -> { | ||
if (accessToken.isM2MToken()) { | ||
if (headers.navIdent.isNullOrBlank()) { | ||
M2MToken(accessToken.claims.getOrThrow(OID)) | ||
} else { | ||
NavAnsatt(accessToken.claims.getOrThrow(OID), headers.navIdent) | ||
} | ||
} else { | ||
NavAnsatt(accessToken.claims.getOrThrow(OID), accessToken.claims.getOrThrow(NavIdent)) | ||
} | ||
} | ||
} | ||
|
||
return SecurityContext( | ||
bruker = bruker, | ||
accessToken = accessToken | ||
) | ||
} |
25 changes: 25 additions & 0 deletions
25
...src/main/kotlin/no/nav/paw/security/authorization/interceptor/AuthorizationInterceptor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package no.nav.paw.security.authorization.interceptor | ||
|
||
import io.ktor.server.application.ApplicationCall | ||
import io.ktor.util.pipeline.PipelineContext | ||
import no.nav.paw.security.authorization.context.AuthorizationContext | ||
import no.nav.paw.security.authorization.context.resolveRequestContext | ||
import no.nav.paw.security.authorization.context.resolveSecurityContext | ||
import no.nav.paw.security.authorization.policy.AccessPolicy | ||
import no.nav.paw.security.authorization.policy.AccessPolicyEvaluator | ||
import org.slf4j.LoggerFactory | ||
|
||
private val logger = LoggerFactory.getLogger("no.nav.paw.logger.security.authorization") | ||
|
||
suspend fun PipelineContext<Unit, ApplicationCall>.authorize( | ||
policies: List<AccessPolicy> = emptyList(), | ||
body: suspend PipelineContext<Unit, ApplicationCall>.(AuthorizationContext) -> Unit | ||
): PipelineContext<Unit, ApplicationCall> { | ||
logger.debug("Kjører autorisasjon") | ||
val requestContext = resolveRequestContext() | ||
val securityContext = requestContext.resolveSecurityContext() | ||
val authorizationContext = AuthorizationContext(requestContext, securityContext) | ||
AccessPolicyEvaluator(policies).checkAccess(authorizationContext) | ||
body(authorizationContext) | ||
return this | ||
} |
7 changes: 7 additions & 0 deletions
7
lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessDecision.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package no.nav.paw.security.authorization.model | ||
|
||
enum class AccessDecision { | ||
PERMIT, | ||
DENY, | ||
DEFER | ||
} |
3 changes: 3 additions & 0 deletions
3
lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessOperation.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package no.nav.paw.security.authorization.model | ||
|
||
enum class AccessOperation { READ, WRITE } |
6 changes: 6 additions & 0 deletions
6
lib/security/src/main/kotlin/no/nav/paw/security/authorization/model/AccessResult.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package no.nav.paw.security.authorization.model | ||
|
||
data class AccessResult( | ||
val decision: AccessDecision, | ||
val details: String | ||
) |
8 changes: 8 additions & 0 deletions
8
lib/security/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicy.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package no.nav.paw.security.authorization.policy | ||
|
||
import no.nav.paw.security.authorization.context.AuthorizationContext | ||
import no.nav.paw.security.authorization.model.AccessResult | ||
|
||
interface AccessPolicy { | ||
fun hasAccess(context: AuthorizationContext): AccessResult | ||
} |
21 changes: 21 additions & 0 deletions
21
...ecurity/src/main/kotlin/no/nav/paw/security/authorization/policy/AccessPolicyEvaluator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package no.nav.paw.security.authorization.policy | ||
|
||
import no.nav.paw.security.authentication.exception.IngenTilgangException | ||
import no.nav.paw.security.authorization.context.AuthorizationContext | ||
import no.nav.paw.security.authorization.model.AccessDecision | ||
import org.slf4j.LoggerFactory | ||
|
||
class AccessPolicyEvaluator(private val policies: List<AccessPolicy>) { | ||
|
||
private val logger = LoggerFactory.getLogger("no.nav.paw.logger.security.authorization") | ||
|
||
fun checkAccess(context: AuthorizationContext) { | ||
logger.debug("Sjekker {} tilgangsregler", policies.size) | ||
val results = policies.map { it.hasAccess(context) } | ||
val deny = results.filter { it.decision == AccessDecision.DENY } | ||
if (deny.isNotEmpty()) { | ||
logger.debug("Evaluering av tilgangsregler resulterte i {} DENY decisions [{}]", deny.size, deny) | ||
throw IngenTilgangException("Ingen tilgang") | ||
} | ||
} | ||
} |
Oops, something went wrong.