Skip to content

Commit 2f7e08e

Browse files
authored
Flexible authentication (#65)
1 parent f2c29e3 commit 2f7e08e

File tree

27 files changed

+211
-129
lines changed

27 files changed

+211
-129
lines changed

Jenkinsfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ pipeline {
1818
}
1919
}
2020
}
21+
post {
22+
always {
23+
cleanWs()
24+
}
25+
}
2126
}

docs/http4k.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ compile "nl.myndocs:oauth2-server-http4k:$myndocs_oauth_version"
2121
```kotlin
2222
val app: HttpHandler = routes(
2323
"/ping" bind GET to { _: Request -> Response(Status.OK).body("pong!") }
24-
) `enable oauth2` {
24+
).enableOauth2 {
2525
identityService = InMemoryIdentity()
2626
.identity {
2727
username = "foo"

oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package nl.myndocs.oauth2
22

3-
import nl.myndocs.oauth2.authenticator.Authorizer
3+
import nl.myndocs.oauth2.authenticator.Credentials
44
import nl.myndocs.oauth2.exception.*
55
import nl.myndocs.oauth2.grant.Granter
66
import nl.myndocs.oauth2.grant.GrantingCall
@@ -11,16 +11,17 @@ import nl.myndocs.oauth2.request.CallContext
1111
import nl.myndocs.oauth2.request.RedirectAuthorizationCodeRequest
1212
import nl.myndocs.oauth2.request.RedirectTokenRequest
1313
import nl.myndocs.oauth2.request.headerCaseInsensitive
14+
import nl.myndocs.oauth2.router.RedirectRouter
15+
import nl.myndocs.oauth2.router.RedirectRouterResponse
1416

1517
class CallRouter(
1618
val tokenEndpoint: String,
1719
val authorizeEndpoint: String,
1820
val tokenInfoEndpoint: String,
1921
private val tokenInfoCallback: (TokenInfo) -> Map<String, Any?>,
2022
private val granters: List<GrantingCall.() -> Granter>,
21-
private val grantingCallFactory: (CallContext) -> GrantingCall,
22-
private val authorizerFactory: (CallContext) -> Authorizer
23-
) {
23+
private val grantingCallFactory: (CallContext) -> GrantingCall
24+
) : RedirectRouter {
2425
companion object {
2526
const val METHOD_POST = "post"
2627
const val METHOD_GET = "get"
@@ -33,11 +34,18 @@ class CallRouter(
3334
fun route(callContext: CallContext) {
3435
when (callContext.path) {
3536
tokenEndpoint -> routeTokenEndpoint(callContext)
36-
authorizeEndpoint -> routeAuthorizeEndpoint(callContext, authorizerFactory(callContext))
3737
tokenInfoEndpoint -> routeTokenInfoEndpoint(callContext)
3838
}
3939
}
4040

41+
override fun route(callContext: CallContext, credentials: Credentials?): RedirectRouterResponse {
42+
return when (callContext.path) {
43+
authorizeEndpoint -> routeAuthorizeEndpoint(callContext, credentials)
44+
else -> throw NoRoutesFoundException("Route '${callContext.path}' not found")
45+
}
46+
}
47+
48+
4149
private fun routeTokenEndpoint(callContext: CallContext) {
4250
if (callContext.method.toLowerCase() != METHOD_POST) {
4351
return
@@ -71,21 +79,18 @@ class CallRouter(
7179

7280
fun routeAuthorizationCodeRedirect(
7381
callContext: CallContext,
74-
authorizer: Authorizer
75-
) {
82+
credentials: Credentials?
83+
): RedirectRouterResponse {
7684
val queryParameters = callContext.queryParameters
77-
val credentials = authorizer.extractCredentials()
7885
try {
7986
val redirect = grantingCallFactory(callContext).redirect(
8087
RedirectAuthorizationCodeRequest(
8188
queryParameters["client_id"],
8289
queryParameters["redirect_uri"],
83-
credentials?.username ?: "",
84-
credentials?.password ?: "",
90+
credentials?.username,
91+
credentials?.password,
8592
queryParameters["scope"]
86-
),
87-
authorizer.authenticator(),
88-
authorizer.scopesVerifier()
93+
)
8994
)
9095

9196
var stateQueryParameter = ""
@@ -95,31 +100,31 @@ class CallRouter(
95100
}
96101

97102
callContext.redirect(queryParameters["redirect_uri"] + "?code=${redirect.codeToken}$stateQueryParameter")
103+
104+
return RedirectRouterResponse(true)
98105
} catch (unverifiedIdentityException: InvalidIdentityException) {
99106
callContext.respondStatus(STATUS_UNAUTHORIZED)
100-
authorizer.failedAuthentication()
107+
108+
return RedirectRouterResponse(false)
101109
}
102110
}
103111

104112

105113
fun routeAccessTokenRedirect(
106114
callContext: CallContext,
107-
authorizer: Authorizer
108-
) {
115+
credentials: Credentials?
116+
): RedirectRouterResponse {
109117
val queryParameters = callContext.queryParameters
110-
val credentials = authorizer.extractCredentials()
111118

112119
try {
113120
val redirect = grantingCallFactory(callContext).redirect(
114121
RedirectTokenRequest(
115122
queryParameters["client_id"],
116123
queryParameters["redirect_uri"],
117-
credentials?.username ?: "",
118-
credentials?.password ?: "",
124+
credentials?.username,
125+
credentials?.password,
119126
queryParameters["scope"]
120-
),
121-
authorizer.authenticator(),
122-
authorizer.scopesVerifier()
127+
)
123128
)
124129

125130
var stateQueryParameter = ""
@@ -133,33 +138,33 @@ class CallRouter(
133138
"&token_type=bearer&expires_in=${redirect.expiresIn()}$stateQueryParameter"
134139
)
135140

141+
return RedirectRouterResponse(true)
136142
} catch (unverifiedIdentityException: InvalidIdentityException) {
137-
authorizer.failedAuthentication()
138143
callContext.respondStatus(STATUS_UNAUTHORIZED)
144+
145+
return RedirectRouterResponse(false)
139146
}
140147
}
141148

142-
private fun routeAuthorizeEndpoint(callContext: CallContext, authorizer: Authorizer) {
149+
private fun routeAuthorizeEndpoint(callContext: CallContext, credentials: Credentials?): RedirectRouterResponse {
143150
try {
144151
if (!arrayOf(METHOD_GET, METHOD_POST).contains(callContext.method.toLowerCase())) {
145-
return
152+
return RedirectRouterResponse(false)
146153
}
147154

148-
val allowedResponseTypes = setOf("code", "token")
149155
val responseType = callContext.queryParameters["response_type"]
150156
?: throw InvalidRequestException("'response_type' not given")
151157

152-
if (!allowedResponseTypes.contains(responseType)) {
153-
throw InvalidGrantException("'grant_type' with value '$responseType' not allowed")
154-
}
155-
156-
when (responseType) {
157-
"code" -> routeAuthorizationCodeRedirect(callContext, authorizer)
158-
"token" -> routeAccessTokenRedirect(callContext, authorizer)
158+
return when (responseType) {
159+
"code" -> routeAuthorizationCodeRedirect(callContext, credentials)
160+
"token" -> routeAccessTokenRedirect(callContext, credentials)
161+
else -> throw InvalidGrantException("'grant_type' with value '$responseType' not allowed")
159162
}
160163
} catch (oauthException: OauthException) {
161164
callContext.respondStatus(STATUS_BAD_REQUEST)
162165
callContext.respondJson(oauthException.toMap())
166+
167+
return RedirectRouterResponse(false)
163168
}
164169
}
165170

oauth2-server-core/src/main/java/nl/myndocs/oauth2/authenticator/Authorizer.kt

Lines changed: 0 additions & 28 deletions
This file was deleted.

oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/CallRouterBuilder.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package nl.myndocs.oauth2.config
22

33
import nl.myndocs.oauth2.CallRouter
4-
import nl.myndocs.oauth2.authenticator.Authorizer
54
import nl.myndocs.oauth2.grant.*
65
import nl.myndocs.oauth2.identity.TokenInfo
76
import nl.myndocs.oauth2.request.CallContext
@@ -20,7 +19,7 @@ internal object CallRouterBuilder {
2019
var granters: List<GrantingCall.() -> Granter> = listOf()
2120
}
2221

23-
fun build(configuration: Configuration, grantingCallFactory: (CallContext) -> GrantingCall, authorizerFactory: (CallContext) -> Authorizer) = CallRouter(
22+
fun build(configuration: Configuration, grantingCallFactory: (CallContext) -> GrantingCall) = CallRouter(
2423
configuration.tokenEndpoint,
2524
configuration.authorizeEndpoint,
2625
configuration.tokenInfoEndpoint,
@@ -31,7 +30,6 @@ internal object CallRouterBuilder {
3130
{ grantClientCredentials() },
3231
{ grantRefreshToken() }
3332
) + configuration.granters,
34-
grantingCallFactory,
35-
authorizerFactory
33+
grantingCallFactory
3634
)
3735
}

oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Configuration.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package nl.myndocs.oauth2.config
22

33
import nl.myndocs.oauth2.CallRouter
4-
import nl.myndocs.oauth2.authenticator.Authorizer
5-
import nl.myndocs.oauth2.request.CallContext
64

75
data class Configuration(
86
val callRouter: CallRouter

oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/ConfigurationBuilder.kt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
package nl.myndocs.oauth2.config
22

3-
import nl.myndocs.oauth2.authenticator.Authorizer
43
import nl.myndocs.oauth2.client.ClientService
54
import nl.myndocs.oauth2.grant.Granter
65
import nl.myndocs.oauth2.grant.GrantingCall
76
import nl.myndocs.oauth2.identity.IdentityService
87
import nl.myndocs.oauth2.identity.TokenInfo
98
import nl.myndocs.oauth2.request.CallContext
10-
import nl.myndocs.oauth2.request.auth.BasicAuthorizer
119
import nl.myndocs.oauth2.response.AccessTokenResponder
1210
import nl.myndocs.oauth2.response.DefaultAccessTokenResponder
1311
import nl.myndocs.oauth2.token.TokenStore
1412
import nl.myndocs.oauth2.token.converter.*
1513

1614
object ConfigurationBuilder {
17-
class Configuration {
15+
open class Configuration {
1816
internal val callRouterConfiguration = CallRouterBuilder.Configuration()
1917

2018
var authorizationEndpoint: String
@@ -47,8 +45,6 @@ object ConfigurationBuilder {
4745
callRouterConfiguration.granters = value
4846
}
4947

50-
var authorizerFactory: (CallContext) -> Authorizer = ::BasicAuthorizer
51-
5248
var identityService: IdentityService? = null
5349
var clientService: ClientService? = null
5450
var tokenStore: TokenStore? = null
@@ -58,8 +54,7 @@ object ConfigurationBuilder {
5854
var accessTokenResponder: AccessTokenResponder = DefaultAccessTokenResponder
5955
}
6056

61-
fun build(configurer: Configuration.() -> Unit): nl.myndocs.oauth2.config.Configuration {
62-
val configuration = Configuration()
57+
fun build(configurer: Configuration.() -> Unit, configuration: Configuration): nl.myndocs.oauth2.config.Configuration {
6358
configurer(configuration)
6459

6560
val grantingCallFactory: (CallContext) -> GrantingCall = { callContext ->
@@ -79,9 +74,13 @@ object ConfigurationBuilder {
7974
return Configuration(
8075
CallRouterBuilder.build(
8176
configuration.callRouterConfiguration,
82-
grantingCallFactory,
83-
configuration.authorizerFactory
77+
grantingCallFactory
8478
)
8579
)
8680
}
81+
fun build(configurer: Configuration.() -> Unit): nl.myndocs.oauth2.config.Configuration {
82+
val configuration = Configuration()
83+
84+
return build(configurer, configuration)
85+
}
8786
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package nl.myndocs.oauth2.exception
2+
3+
class NoRoutesFoundException : Exception {
4+
constructor() : super()
5+
constructor(message: String?) : super(message)
6+
constructor(message: String?, cause: Throwable?) : super(message, cause)
7+
constructor(cause: Throwable?) : super(cause)
8+
}

oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterDefault.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,13 @@ internal val INVALID_REQUEST_FIELD_MESSAGE = "'%s' field is missing"
6464
fun GrantingCall.validateScopes(
6565
client: Client,
6666
identity: Identity,
67-
requestedScopes: Set<String>,
68-
identityScopeVerifier: IdentityScopeVerifier? = null) {
67+
requestedScopes: Set<String>) {
6968
val scopesAllowed = scopesAllowed(client.clientScopes, requestedScopes)
7069
if (!scopesAllowed) {
7170
throw InvalidScopeException(requestedScopes.minus(client.clientScopes))
7271
}
7372

74-
val allowedScopes = identityScopeVerifier?.allowedScopes(client, identity, requestedScopes)
75-
?: identityService.allowedScopes(client, identity, requestedScopes)
73+
val allowedScopes = identityService.allowedScopes(client, identity, requestedScopes)
7674

7775
val ivalidScopes = requestedScopes.minus(allowedScopes)
7876
if (ivalidScopes.isNotEmpty()) {

0 commit comments

Comments
 (0)