diff --git a/README.md b/README.md
index c8c33d6..e05c11c 100644
--- a/README.md
+++ b/README.md
@@ -51,210 +51,27 @@ For the frameworks examples we need at least the following dependencies:
```
-## Ktor
-The following dependency is required along with the dependencies described in Setup
-
-```xml
-
- nl.myndocs
- oauth2-server-ktor
- ${myndocs.oauth.version}
-
-```
-
-In memory example for Ktor:
-```kotlin
-embeddedServer(Netty, 8080) {
- install(Oauth2ServerFeature) {
- tokenService = Oauth2TokenServiceBuilder.build {
- identityService = InMemoryIdentity()
- .identity {
- username = "foo"
- password = "bar"
- }
- clientService = InMemoryClient()
- .client {
- clientId = "testapp"
- clientSecret = "testpass"
- scopes = setOf("trusted")
- redirectUris = setOf("https://localhost:8080/callback")
- authorizedGrantTypes = setOf(
- AuthorizedGrantType.AUTHORIZATION_CODE,
- AuthorizedGrantType.PASSWORD,
- AuthorizedGrantType.IMPLICIT,
- AuthorizedGrantType.REFRESH_TOKEN
- )
- }
- tokenStore = InMemoryTokenStore()
- }
- }
-}.start(wait = true)
-```
-
-## Javalin
-The following dependency is required along with the dependencies described in Setup
-```xml
-
- nl.myndocs
- oauth2-server-javalin
- ${myndocs.oauth.version}
-
-```
-
-In memory example for Javalin:
-```kotlin
-Javalin.create().apply {
- enableOauthServer {
- tokenService = Oauth2TokenServiceBuilder.build {
- identityService = InMemoryIdentity()
- .identity {
- username = "foo"
- password = "bar"
- }
- clientService = InMemoryClient()
- .client {
- clientId = "testapp"
- clientSecret = "testpass"
- scopes = setOf("trusted")
- redirectUris = setOf("https://localhost:7000/callback")
- authorizedGrantTypes = setOf(
- AuthorizedGrantType.AUTHORIZATION_CODE,
- AuthorizedGrantType.PASSWORD,
- AuthorizedGrantType.IMPLICIT,
- AuthorizedGrantType.REFRESH_TOKEN
- )
- }
- tokenStore = InMemoryTokenStore()
- }
-
- }
-}.start(7000)
-```
-
-## Spark java
-The following dependency is required along with the dependencies described in Setup
-```xml
-
- nl.myndocs
- oauth2-server-sparkjava
- ${myndocs.oauth.version}
-
-```
-
-In memory example for Spark java:
-```kotlin
-Oauth2Server.configureOauth2Server {
- tokenService = Oauth2TokenServiceBuilder.build {
- identityService = InMemoryIdentity()
- .identity {
- username = "foo"
- password = "bar"
- }
- clientService = InMemoryClient()
- .client {
- clientId = "testapp"
- clientSecret = "testpass"
- scopes = setOf("trusted")
- redirectUris = setOf("https://localhost:4567/callback")
- authorizedGrantTypes = setOf(
- AuthorizedGrantType.AUTHORIZATION_CODE,
- AuthorizedGrantType.PASSWORD,
- AuthorizedGrantType.IMPLICIT,
- AuthorizedGrantType.REFRESH_TOKEN
- )
- }
- tokenStore = InMemoryTokenStore()
- }
-}
-```
-## http4k
-The following dependency is required along with the dependencies described in Setup
-```xml
-
- nl.myndocs
- oauth2-server-http4k
- ${myndocs.oauth.version}
-
-```
-
-In memory example for http4k:
-```kotlin
-val app: HttpHandler = routes(
- "/ping" bind GET to { _: Request -> Response(Status.OK).body("pong!") }
- ) `enable oauth2` {
- tokenService = Oauth2TokenServiceBuilder.build {
- identityService = InMemoryIdentity()
- .identity {
- username = "foo"
- password = "bar"
- }
- clientService = InMemoryClient()
- .client {
- clientId = "testapp"
- clientSecret = "testpass"
- scopes = setOf("trusted")
- redirectUris = setOf("http://localhost:8080/callback")
- authorizedGrantTypes = setOf(
- AuthorizedGrantType.AUTHORIZATION_CODE,
- AuthorizedGrantType.PASSWORD,
- AuthorizedGrantType.IMPLICIT,
- AuthorizedGrantType.REFRESH_TOKEN
- )
- }
- tokenStore = InMemoryTokenStore()
- }
- }
-
- app.asServer(Jetty(9000)).start()
-```
-
-**Note:** `/ping` is only added for demonstration for own defined routes.
-# Custom implementation
-## Identity service
-Users can be authenticate through the identity service. In OAuth2 terms this would be the resource owner.
-
-```kotlin
-fun identityOf(forClient: Client, username: String): Identity?
-
-fun validCredentials(forClient: Client, identity: Identity, password: String): Boolean
-
-fun allowedScopes(forClient: Client, identity: Identity, scopes: Set): Set
-```
-
-Each of the methods that needs to be implemented contains `Client`. This could give you extra flexibility.
-For example you could have user base per client, instead of have users over all clients.
-
-## Client service
-Client service is similar to the identity service.
-
-```kotlin
-fun clientOf(clientId: String): Client?
-
-fun validClient(client: Client, clientSecret: String): Boolean
-```
-
-## Token store
-The following methods have to be implemented for a token store.
-
+### Framework implementation
+The following frameworks are supported:
+- [Ktor](docs/ktor.md)
+- [Javalin](docs/javalin.md)
+- [http4k](docs/http4k.md)
+- [Sparkjava](docs/sparkjava.md)
+
+## Configuration
+### Routing
+Default endpoints are configured:
+
+| Type | Relative url |
+| ----- | ------------- |
+| token | /oauth/token |
+| authorize | /oauth/authorize |
+| token info | /oauth/tokeninfo |
+
+These values can be overridden:
```kotlin
-fun storeAccessToken(accessToken: AccessToken)
-
-fun accessToken(token: String): AccessToken?
-
-fun revokeAccessToken(token: String)
-
-fun storeCodeToken(codeToken: CodeToken)
-
-fun codeToken(token: String): CodeToken?
-
-fun consumeCodeToken(token: String): CodeToken?
-
-fun storeRefreshToken(refreshToken: RefreshToken)
-
-fun refreshToken(token: String): RefreshToken?
-
-fun revokeRefreshToken(token: String)
-
+tokenEndpoint = "/custom/token"
+authorizationEndpoint = "/custom/authorize"
+tokenInfoEndpoint = "/custom/tokeninfo"
```
-When `AccessToken` is passed to `storeAccessToken` and it contains a `RefreshToken`, then `storeAccessToken` is also responsible for saving the refresh token.
diff --git a/docs/http4k.md b/docs/http4k.md
new file mode 100644
index 0000000..e20f2f3
--- /dev/null
+++ b/docs/http4k.md
@@ -0,0 +1,39 @@
+# http4k
+
+## Dependencies
+```xml
+
+ nl.myndocs
+ oauth2-server-http4k
+ ${myndocs.oauth.version}
+
+```
+
+## Implementation
+```kotlin
+val app: HttpHandler = routes(
+ "/ping" bind GET to { _: Request -> Response(Status.OK).body("pong!") }
+ ) `enable oauth2` {
+ identityService = InMemoryIdentity()
+ .identity {
+ username = "foo"
+ password = "bar"
+ }
+ clientService = InMemoryClient()
+ .client {
+ clientId = "testapp"
+ clientSecret = "testpass"
+ scopes = setOf("trusted")
+ redirectUris = setOf("http://localhost:8080/callback")
+ authorizedGrantTypes = setOf(
+ AuthorizedGrantType.AUTHORIZATION_CODE,
+ AuthorizedGrantType.PASSWORD,
+ AuthorizedGrantType.IMPLICIT,
+ AuthorizedGrantType.REFRESH_TOKEN
+ )
+ }
+ tokenStore = InMemoryTokenStore()
+ }
+
+ app.asServer(Jetty(9000)).start()
+```
diff --git a/docs/javalin.md b/docs/javalin.md
new file mode 100644
index 0000000..2c9c065
--- /dev/null
+++ b/docs/javalin.md
@@ -0,0 +1,37 @@
+# Javalin
+
+## Dependencies
+```xml
+
+ nl.myndocs
+ oauth2-server-javalin
+ ${myndocs.oauth.version}
+
+```
+
+## Implementation
+```kotlin
+Javalin.create().apply {
+ enableOauthServer {
+ identityService = InMemoryIdentity()
+ .identity {
+ username = "foo"
+ password = "bar"
+ }
+ clientService = InMemoryClient()
+ .client {
+ clientId = "testapp"
+ clientSecret = "testpass"
+ scopes = setOf("trusted")
+ redirectUris = setOf("https://localhost:7000/callback")
+ authorizedGrantTypes = setOf(
+ AuthorizedGrantType.AUTHORIZATION_CODE,
+ AuthorizedGrantType.PASSWORD,
+ AuthorizedGrantType.IMPLICIT,
+ AuthorizedGrantType.REFRESH_TOKEN
+ )
+ }
+ tokenStore = InMemoryTokenStore()
+ }
+}.start(7000)
+```
diff --git a/docs/ktor.md b/docs/ktor.md
new file mode 100644
index 0000000..31cb28d
--- /dev/null
+++ b/docs/ktor.md
@@ -0,0 +1,38 @@
+# Ktor
+
+## Dependencies
+
+```xml
+
+ nl.myndocs
+ oauth2-server-ktor
+ ${myndocs.oauth.version}
+
+```
+
+## Implementation
+```kotlin
+embeddedServer(Netty, 8080) {
+ install(Oauth2ServerFeature) {
+ identityService = InMemoryIdentity()
+ .identity {
+ username = "foo"
+ password = "bar"
+ }
+ clientService = InMemoryClient()
+ .client {
+ clientId = "testapp"
+ clientSecret = "testpass"
+ scopes = setOf("trusted")
+ redirectUris = setOf("https://localhost:8080/callback")
+ authorizedGrantTypes = setOf(
+ AuthorizedGrantType.AUTHORIZATION_CODE,
+ AuthorizedGrantType.PASSWORD,
+ AuthorizedGrantType.IMPLICIT,
+ AuthorizedGrantType.REFRESH_TOKEN
+ )
+ }
+ tokenStore = InMemoryTokenStore()
+ }
+}.start(wait = true)
+```
\ No newline at end of file
diff --git a/docs/sparkjava.md b/docs/sparkjava.md
new file mode 100644
index 0000000..f4f8d57
--- /dev/null
+++ b/docs/sparkjava.md
@@ -0,0 +1,35 @@
+# Spark java
+
+## Dependencies
+```xml
+
+ nl.myndocs
+ oauth2-server-sparkjava
+ ${myndocs.oauth.version}
+
+```
+
+## Implementation
+```kotlin
+Oauth2Server.configureOauth2Server {
+ identityService = InMemoryIdentity()
+ .identity {
+ username = "foo"
+ password = "bar"
+ }
+ clientService = InMemoryClient()
+ .client {
+ clientId = "testapp"
+ clientSecret = "testpass"
+ scopes = setOf("trusted")
+ redirectUris = setOf("https://localhost:4567/callback")
+ authorizedGrantTypes = setOf(
+ AuthorizedGrantType.AUTHORIZATION_CODE,
+ AuthorizedGrantType.PASSWORD,
+ AuthorizedGrantType.IMPLICIT,
+ AuthorizedGrantType.REFRESH_TOKEN
+ )
+ }
+ tokenStore = InMemoryTokenStore()
+}
+```
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt
index a0e526e..fdc86ff 100644
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt
@@ -4,6 +4,8 @@ import nl.myndocs.oauth2.authenticator.Authorizer
import nl.myndocs.oauth2.exception.*
import nl.myndocs.oauth2.grant.Granter
import nl.myndocs.oauth2.grant.GrantingCall
+import nl.myndocs.oauth2.grant.redirect
+import nl.myndocs.oauth2.grant.tokenInfo
import nl.myndocs.oauth2.identity.TokenInfo
import nl.myndocs.oauth2.request.CallContext
import nl.myndocs.oauth2.request.RedirectAuthorizationCodeRequest
@@ -11,12 +13,12 @@ import nl.myndocs.oauth2.request.RedirectTokenRequest
import nl.myndocs.oauth2.request.headerCaseInsensitive
class CallRouter(
- private val tokenService: TokenService,
val tokenEndpoint: String,
val authorizeEndpoint: String,
val tokenInfoEndpoint: String,
private val tokenInfoCallback: (TokenInfo) -> Map,
- private val granters: List Granter>
+ private val granters: List Granter>,
+ private val grantingCallFactory: (CallContext) -> GrantingCall
) {
companion object {
const val METHOD_POST = "post"
@@ -46,12 +48,7 @@ class CallRouter(
val grantType = callContext.formParameters["grant_type"]
?: throw InvalidRequestException("'grant_type' not given")
- val grantingCall = object: GrantingCall {
- override val callContext: CallContext
- get() = callContext
-
- override val tokenService = this@CallRouter.tokenService
- }
+ val grantingCall = grantingCallFactory(callContext)
val granterMap = granters
.map {
@@ -75,13 +72,12 @@ class CallRouter(
fun routeAuthorizationCodeRedirect(
callContext: CallContext,
- tokenService: TokenService,
authorizer: Authorizer
) {
val queryParameters = callContext.queryParameters
val credentials = authorizer.extractCredentials()
try {
- val redirect = tokenService.redirect(
+ val redirect = grantingCallFactory(callContext).redirect(
RedirectAuthorizationCodeRequest(
queryParameters["client_id"],
queryParameters["redirect_uri"],
@@ -109,14 +105,13 @@ class CallRouter(
fun routeAccessTokenRedirect(
callContext: CallContext,
- tokenService: TokenService,
authorizer: Authorizer
) {
val queryParameters = callContext.queryParameters
val credentials = authorizer.extractCredentials()
try {
- val redirect = tokenService.redirect(
+ val redirect = grantingCallFactory(callContext).redirect(
RedirectTokenRequest(
queryParameters["client_id"],
queryParameters["redirect_uri"],
@@ -160,8 +155,8 @@ class CallRouter(
}
when (responseType) {
- "code" -> routeAuthorizationCodeRedirect(callContext, tokenService, authorizer)
- "token" -> routeAccessTokenRedirect(callContext, tokenService, authorizer)
+ "code" -> routeAuthorizationCodeRedirect(callContext, authorizer)
+ "token" -> routeAccessTokenRedirect(callContext, authorizer)
}
} catch (oauthException: OauthException) {
callContext.respondStatus(STATUS_BAD_REQUEST)
@@ -183,7 +178,7 @@ class CallRouter(
val token = authorization.substring(7)
- val tokenInfoCallback = tokenInfoCallback(tokenService.tokenInfo(token))
+ val tokenInfoCallback = tokenInfoCallback(grantingCallFactory(callContext).tokenInfo(token))
callContext.respondJson(tokenInfoCallback)
}
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/Oauth2TokenService.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/Oauth2TokenService.kt
deleted file mode 100644
index 7ccaca3..0000000
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/Oauth2TokenService.kt
+++ /dev/null
@@ -1,354 +0,0 @@
-package nl.myndocs.oauth2
-
-import nl.myndocs.oauth2.authenticator.Authenticator
-import nl.myndocs.oauth2.authenticator.IdentityScopeVerifier
-import nl.myndocs.oauth2.client.AuthorizedGrantType
-import nl.myndocs.oauth2.client.Client
-import nl.myndocs.oauth2.client.ClientService
-import nl.myndocs.oauth2.exception.*
-import nl.myndocs.oauth2.identity.Identity
-import nl.myndocs.oauth2.identity.IdentityService
-import nl.myndocs.oauth2.identity.TokenInfo
-import nl.myndocs.oauth2.request.*
-import nl.myndocs.oauth2.response.TokenResponse
-import nl.myndocs.oauth2.scope.ScopeParser
-import nl.myndocs.oauth2.token.AccessToken
-import nl.myndocs.oauth2.token.CodeToken
-import nl.myndocs.oauth2.token.TokenStore
-import nl.myndocs.oauth2.token.converter.AccessTokenConverter
-import nl.myndocs.oauth2.token.converter.CodeTokenConverter
-import nl.myndocs.oauth2.token.converter.RefreshTokenConverter
-
-class Oauth2TokenService(
- private val identityService: IdentityService,
- private val clientService: ClientService,
- private val tokenStore: TokenStore,
- private val accessTokenConverter: AccessTokenConverter,
- private val refreshTokenConverter: RefreshTokenConverter,
- private val codeTokenConverter: CodeTokenConverter
-) : TokenService {
- private val INVALID_REQUEST_FIELD_MESSAGE = "'%s' field is missing"
- /**
- * @throws InvalidIdentityException
- * @throws InvalidClientException
- * @throws InvalidScopeException
- */
- override fun authorize(passwordGrantRequest: PasswordGrantRequest): TokenResponse {
- throwExceptionIfUnverifiedClient(passwordGrantRequest)
-
- if (passwordGrantRequest.username == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
- }
-
- if (passwordGrantRequest.password == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
- }
-
- val requestedClient = clientService.clientOf(passwordGrantRequest.clientId!!) ?: throw InvalidClientException()
-
- val authorizedGrantType = AuthorizedGrantType.PASSWORD
- if (!requestedClient.authorizedGrantTypes.contains(authorizedGrantType)) {
- throw InvalidGrantException("Authorize not allowed: '$authorizedGrantType'")
- }
-
- val requestedIdentity = identityService.identityOf(
- requestedClient, passwordGrantRequest.username
- )
-
- if (requestedIdentity == null || !identityService.validCredentials(requestedClient, requestedIdentity, passwordGrantRequest.password)) {
- throw InvalidIdentityException()
- }
-
- var requestedScopes = ScopeParser.parseScopes(passwordGrantRequest.scope)
- .toSet()
-
- if (passwordGrantRequest.scope == null) {
- requestedScopes = requestedClient.clientScopes
- }
-
- validateScopes(requestedClient, requestedIdentity, requestedScopes)
-
- val accessToken = accessTokenConverter.convertToToken(
- requestedIdentity.username,
- requestedClient.clientId,
- requestedScopes,
- refreshTokenConverter.convertToToken(
- requestedIdentity.username,
- requestedClient.clientId,
- requestedScopes
- )
- )
-
- tokenStore.storeAccessToken(accessToken)
-
- return accessToken.toTokenResponse()
- }
-
- override fun authorize(authorizationCodeRequest: AuthorizationCodeRequest): TokenResponse {
- throwExceptionIfUnverifiedClient(authorizationCodeRequest)
-
- if (authorizationCodeRequest.code == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("code"))
- }
-
- if (authorizationCodeRequest.redirectUri == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
- }
-
- val consumeCodeToken = tokenStore.consumeCodeToken(authorizationCodeRequest.code)
- ?: throw InvalidGrantException()
-
-
- if (consumeCodeToken.redirectUri != authorizationCodeRequest.redirectUri || consumeCodeToken.clientId != authorizationCodeRequest.clientId) {
- throw InvalidGrantException()
- }
-
- val accessToken = accessTokenConverter.convertToToken(
- consumeCodeToken.username,
- consumeCodeToken.clientId,
- consumeCodeToken.scopes,
- refreshTokenConverter.convertToToken(
- consumeCodeToken.username,
- consumeCodeToken.clientId,
- consumeCodeToken.scopes
- )
- )
-
- tokenStore.storeAccessToken(accessToken)
-
- return accessToken.toTokenResponse()
- }
-
- override fun authorize(clientCredentialsRequest: ClientCredentialsRequest): TokenResponse {
- throwExceptionIfUnverifiedClient(clientCredentialsRequest)
-
- val requestedClient = clientService.clientOf(clientCredentialsRequest.clientId!!) ?: throw InvalidClientException()
-
- val scopes = clientCredentialsRequest.scope
- ?.let { ScopeParser.parseScopes(it).toSet() }
- ?: requestedClient.clientScopes
-
- val accessToken = accessTokenConverter.convertToToken(
- username = null,
- clientId = clientCredentialsRequest.clientId,
- requestedScopes = scopes,
- refreshToken = refreshTokenConverter.convertToToken(
- username = null,
- clientId = clientCredentialsRequest.clientId,
- requestedScopes = scopes
- )
- )
-
- tokenStore.storeAccessToken(accessToken)
-
- return accessToken.toTokenResponse()
- }
-
- override fun refresh(refreshTokenRequest: RefreshTokenRequest): TokenResponse {
- throwExceptionIfUnverifiedClient(refreshTokenRequest)
-
- if (refreshTokenRequest.refreshToken == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("refresh_token"))
- }
-
- val refreshToken = tokenStore.refreshToken(refreshTokenRequest.refreshToken) ?: throw InvalidGrantException()
-
- if (refreshToken.clientId != refreshTokenRequest.clientId) {
- throw InvalidGrantException()
- }
-
- val client = clientService.clientOf(refreshToken.clientId) ?: throw InvalidClientException()
-
- val authorizedGrantType = AuthorizedGrantType.REFRESH_TOKEN
- if (!client.authorizedGrantTypes.contains(authorizedGrantType)) {
- throw InvalidGrantException("Authorize not allowed: '$authorizedGrantType'")
- }
-
- val accessToken = accessTokenConverter.convertToToken(
- refreshToken.username,
- refreshToken.clientId,
- refreshToken.scopes,
- refreshTokenConverter.convertToToken(refreshToken)
- )
-
- tokenStore.storeAccessToken(accessToken)
-
- return accessToken.toTokenResponse()
- }
-
- override fun redirect(
- redirect: RedirectAuthorizationCodeRequest,
- authenticator: Authenticator?,
- identityScopeVerifier: IdentityScopeVerifier?
- ): CodeToken {
- if (redirect.clientId == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
- }
-
- if (redirect.username == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
- }
-
- if (redirect.password == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
- }
- if (redirect.redirectUri == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
- }
-
- val clientOf = clientService.clientOf(redirect.clientId) ?: throw InvalidClientException()
-
- if (!clientOf.redirectUris.contains(redirect.redirectUri)) {
- throw InvalidGrantException("invalid 'redirect_uri'")
- }
-
- val authorizedGrantType = AuthorizedGrantType.AUTHORIZATION_CODE
- if (!clientOf.authorizedGrantTypes.contains(authorizedGrantType)) {
- throw InvalidGrantException("Authorize not allowed: '$authorizedGrantType'")
- }
-
- val identityOf = identityService.identityOf(clientOf, redirect.username) ?: throw InvalidIdentityException()
-
- var validIdentity = authenticator?.validCredentials(clientOf, identityOf, redirect.password)
- ?: identityService.validCredentials(clientOf, identityOf, redirect.password)
-
- if (!validIdentity) {
- throw InvalidIdentityException()
- }
-
- var requestedScopes = ScopeParser.parseScopes(redirect.scope)
-
- if (redirect.scope == null) {
- requestedScopes = clientOf.clientScopes
- }
-
- validateScopes(clientOf, identityOf, requestedScopes, identityScopeVerifier)
-
- val codeToken = codeTokenConverter.convertToToken(
- identityOf.username,
- clientOf.clientId,
- redirect.redirectUri,
- requestedScopes
- )
-
- tokenStore.storeCodeToken(codeToken)
-
- return codeToken
- }
-
- override fun redirect(
- redirect: RedirectTokenRequest,
- authenticator: Authenticator?,
- identityScopeVerifier: IdentityScopeVerifier?
- ): AccessToken {
- if (redirect.clientId == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
- }
-
- if (redirect.username == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
- }
-
- if (redirect.password == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
- }
- if (redirect.redirectUri == null) {
- throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
- }
-
- val clientOf = clientService.clientOf(redirect.clientId) ?: throw InvalidClientException()
-
- if (!clientOf.redirectUris.contains(redirect.redirectUri)) {
- throw InvalidGrantException("invalid 'redirect_uri'")
- }
-
- val authorizedGrantType = AuthorizedGrantType.IMPLICIT
- if (!clientOf.authorizedGrantTypes.contains(authorizedGrantType)) {
- throw InvalidGrantException("Authorize not allowed: '$authorizedGrantType'")
- }
-
- val identityOf = identityService.identityOf(clientOf, redirect.username) ?: throw InvalidIdentityException()
-
- var validIdentity = authenticator?.validCredentials(clientOf, identityOf, redirect.password)
- ?: identityService.validCredentials(clientOf, identityOf, redirect.password)
-
- if (!validIdentity) {
- throw InvalidIdentityException()
- }
-
- var requestedScopes = ScopeParser.parseScopes(redirect.scope)
-
- if (redirect.scope == null) {
- requestedScopes = clientOf.clientScopes
- }
-
- validateScopes(clientOf, identityOf, requestedScopes, identityScopeVerifier)
-
- val accessToken = accessTokenConverter.convertToToken(
- identityOf.username,
- clientOf.clientId,
- requestedScopes,
- null
- )
-
- tokenStore.storeAccessToken(accessToken)
-
- return accessToken
- }
-
- private fun validateScopes(
- client: Client,
- identity: Identity,
- requestedScopes: Set,
- identityScopeVerifier: IdentityScopeVerifier? = null) {
- val scopesAllowed = scopesAllowed(client.clientScopes, requestedScopes)
- if (!scopesAllowed) {
- throw InvalidScopeException(requestedScopes.minus(client.clientScopes))
- }
-
- val allowedScopes = identityScopeVerifier?.allowedScopes(client, identity, requestedScopes)
- ?: identityService.allowedScopes(client, identity, requestedScopes)
-
- val ivalidScopes = requestedScopes.minus(allowedScopes)
- if (ivalidScopes.isNotEmpty()) {
- throw InvalidScopeException(ivalidScopes)
- }
- }
-
- override fun tokenInfo(accessToken: String): TokenInfo {
- val storedAccessToken = tokenStore.accessToken(accessToken) ?: throw InvalidGrantException()
- val client = clientService.clientOf(storedAccessToken.clientId) ?: throw InvalidClientException()
- val identity = storedAccessToken.username?.let { identityService.identityOf(client, it) }
-
- return TokenInfo(
- identity,
- client,
- storedAccessToken.scopes
- )
- }
-
- private fun throwExceptionIfUnverifiedClient(clientRequest: ClientRequest) {
- val clientId = clientRequest.clientId
- ?: throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
-
- val clientSecret = clientRequest.clientSecret
- ?: throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_secret"))
-
- val client = clientService.clientOf(clientId) ?: throw InvalidClientException()
-
- if (!clientService.validClient(client, clientSecret)) {
- throw InvalidClientException()
- }
- }
-
- private fun scopesAllowed(clientScopes: Set, requestedScopes: Set): Boolean {
- return clientScopes.containsAll(requestedScopes)
- }
-
- private fun AccessToken.toTokenResponse() = TokenResponse(
- accessToken,
- tokenType,
- expiresIn(),
- refreshToken?.refreshToken
- )
-}
\ No newline at end of file
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/TokenService.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/TokenService.kt
deleted file mode 100644
index 215405e..0000000
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/TokenService.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package nl.myndocs.oauth2
-
-import nl.myndocs.oauth2.authenticator.Authenticator
-import nl.myndocs.oauth2.authenticator.IdentityScopeVerifier
-import nl.myndocs.oauth2.identity.TokenInfo
-import nl.myndocs.oauth2.request.*
-import nl.myndocs.oauth2.response.TokenResponse
-import nl.myndocs.oauth2.token.AccessToken
-import nl.myndocs.oauth2.token.CodeToken
-
-interface TokenService {
- fun authorize(passwordGrantRequest: PasswordGrantRequest): TokenResponse
-
- fun authorize(authorizationCodeRequest: AuthorizationCodeRequest): TokenResponse
-
- fun authorize(clientCredentialsRequest: ClientCredentialsRequest): TokenResponse
-
- fun refresh(refreshTokenRequest: RefreshTokenRequest): TokenResponse
-
- fun redirect(
- redirect: RedirectAuthorizationCodeRequest,
- authenticator: Authenticator?,
- identityScopeVerifier: IdentityScopeVerifier?
- ): CodeToken
-
- fun redirect(
- redirect: RedirectTokenRequest,
- authenticator: Authenticator?,
- identityScopeVerifier: IdentityScopeVerifier?
- ): AccessToken
-
- fun tokenInfo(accessToken: String): TokenInfo
-}
\ No newline at end of file
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/CallRouterBuilder.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/CallRouterBuilder.kt
index 710ef13..89eca27 100644
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/CallRouterBuilder.kt
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/CallRouterBuilder.kt
@@ -1,8 +1,9 @@
package nl.myndocs.oauth2.config
-import nl.myndocs.oauth2.*
+import nl.myndocs.oauth2.CallRouter
import nl.myndocs.oauth2.grant.*
import nl.myndocs.oauth2.identity.TokenInfo
+import nl.myndocs.oauth2.request.CallContext
internal object CallRouterBuilder {
class Configuration {
@@ -15,19 +16,10 @@ internal object CallRouterBuilder {
"scopes" to tokenInfo.scopes
).filterValues { it != null }
}
- var tokenService: TokenService? = null
var granters: List Granter> = listOf()
}
- fun build(configurer: Configuration.() -> Unit): CallRouter {
- val configuration = Configuration()
- configurer(configuration)
-
- return build(configuration)
- }
-
- fun build(configuration: Configuration) = CallRouter(
- configuration.tokenService!!,
+ fun build(configuration: Configuration, grantingCallFactory: (CallContext) -> GrantingCall) = CallRouter(
configuration.tokenEndpoint,
configuration.authorizeEndpoint,
configuration.tokenInfoEndpoint,
@@ -37,6 +29,7 @@ internal object CallRouterBuilder {
{ grantAuthorizationCode() },
{ grantClientCredentials() },
{ grantRefreshToken() }
- ) + configuration.granters
+ ) + configuration.granters,
+ grantingCallFactory
)
}
\ No newline at end of file
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Configuration.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Configuration.kt
index 90b8a19..67e4044 100644
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Configuration.kt
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Configuration.kt
@@ -1,12 +1,10 @@
package nl.myndocs.oauth2.config
import nl.myndocs.oauth2.CallRouter
-import nl.myndocs.oauth2.TokenService
import nl.myndocs.oauth2.authenticator.Authorizer
import nl.myndocs.oauth2.request.CallContext
data class Configuration(
- val tokenService: TokenService,
val callRouter: CallRouter,
val authorizerFactory: (CallContext) -> Authorizer
)
\ No newline at end of file
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/ConfigurationBuilder.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/ConfigurationBuilder.kt
index 0f69b0e..eee7012 100644
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/ConfigurationBuilder.kt
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/ConfigurationBuilder.kt
@@ -1,23 +1,20 @@
package nl.myndocs.oauth2.config
-import nl.myndocs.oauth2.TokenService
import nl.myndocs.oauth2.authenticator.Authorizer
+import nl.myndocs.oauth2.client.ClientService
import nl.myndocs.oauth2.grant.Granter
import nl.myndocs.oauth2.grant.GrantingCall
+import nl.myndocs.oauth2.identity.IdentityService
import nl.myndocs.oauth2.identity.TokenInfo
import nl.myndocs.oauth2.request.CallContext
import nl.myndocs.oauth2.request.auth.BasicAuthorizer
+import nl.myndocs.oauth2.token.TokenStore
+import nl.myndocs.oauth2.token.converter.*
object ConfigurationBuilder {
class Configuration {
internal val callRouterConfiguration = CallRouterBuilder.Configuration()
- var tokenService: TokenService?
- get() = callRouterConfiguration.tokenService
- set(value) {
- callRouterConfiguration.tokenService = value
- }
-
var authorizationEndpoint: String
get() = callRouterConfiguration.authorizeEndpoint
set(value) {
@@ -49,15 +46,34 @@ object ConfigurationBuilder {
}
var authorizerFactory: (CallContext) -> Authorizer = ::BasicAuthorizer
+
+ var identityService: IdentityService? = null
+ var clientService: ClientService? = null
+ var tokenStore: TokenStore? = null
+ var accessTokenConverter: AccessTokenConverter = UUIDAccessTokenConverter()
+ var refreshTokenConverter: RefreshTokenConverter = UUIDRefreshTokenConverter()
+ var codeTokenConverter: CodeTokenConverter = UUIDCodeTokenConverter()
}
fun build(configurer: Configuration.() -> Unit): nl.myndocs.oauth2.config.Configuration {
val configuration = Configuration()
configurer(configuration)
+ val grantingCallFactory: (CallContext) -> GrantingCall = { callContext ->
+ object : GrantingCall {
+ override val callContext = callContext
+ override val identityService = configuration.identityService!!
+ override val clientService = configuration.clientService!!
+ override val tokenStore = configuration.tokenStore!!
+ override val converters = Converters(
+ configuration.accessTokenConverter,
+ configuration.refreshTokenConverter,
+ configuration.codeTokenConverter
+ )
+ }
+ }
return nl.myndocs.oauth2.config.Configuration(
- configuration.tokenService!!,
- CallRouterBuilder.build(configuration.callRouterConfiguration),
+ CallRouterBuilder.build(configuration.callRouterConfiguration, grantingCallFactory),
configuration.authorizerFactory
)
}
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Oauth2TokenServiceBuilder.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Oauth2TokenServiceBuilder.kt
deleted file mode 100644
index 7fbc9a3..0000000
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Oauth2TokenServiceBuilder.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package nl.myndocs.oauth2.config
-
-import nl.myndocs.oauth2.Oauth2TokenService
-import nl.myndocs.oauth2.client.ClientService
-import nl.myndocs.oauth2.identity.IdentityService
-import nl.myndocs.oauth2.token.TokenStore
-import nl.myndocs.oauth2.token.converter.*
-
-object Oauth2TokenServiceBuilder {
- class Configuration {
- var identityService: IdentityService? = null
- var clientService: ClientService? = null
- var tokenStore: TokenStore? = null
- var accessTokenConverter: AccessTokenConverter = UUIDAccessTokenConverter()
- var refreshTokenConverter: RefreshTokenConverter = UUIDRefreshTokenConverter()
- var codeTokenConverter: CodeTokenConverter = UUIDCodeTokenConverter()
- }
-
- fun build(configurer: Configuration.() -> Unit): Oauth2TokenService {
- val configuration = Configuration()
- configurer(configuration)
-
- return Oauth2TokenService(
- configuration.identityService!!,
- configuration.clientService!!,
- configuration.tokenStore!!,
- configuration.accessTokenConverter,
- configuration.refreshTokenConverter,
- configuration.codeTokenConverter
- )
- }
-}
\ No newline at end of file
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterAuthorize.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterAuthorize.kt
new file mode 100644
index 0000000..a3c873e
--- /dev/null
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterAuthorize.kt
@@ -0,0 +1,126 @@
+package nl.myndocs.oauth2.grant
+
+import nl.myndocs.oauth2.client.AuthorizedGrantType
+import nl.myndocs.oauth2.exception.*
+import nl.myndocs.oauth2.request.AuthorizationCodeRequest
+import nl.myndocs.oauth2.request.ClientCredentialsRequest
+import nl.myndocs.oauth2.request.PasswordGrantRequest
+import nl.myndocs.oauth2.response.TokenResponse
+import nl.myndocs.oauth2.scope.ScopeParser
+
+
+/**
+ * @throws InvalidIdentityException
+ * @throws InvalidClientException
+ * @throws InvalidScopeException
+ */
+fun GrantingCall.authorize(passwordGrantRequest: PasswordGrantRequest): TokenResponse {
+ throwExceptionIfUnverifiedClient(passwordGrantRequest)
+
+ if (passwordGrantRequest.username == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
+ }
+
+ if (passwordGrantRequest.password == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
+ }
+
+ val requestedClient = clientService.clientOf(passwordGrantRequest.clientId!!) ?: throw InvalidClientException()
+
+ val authorizedGrantType = AuthorizedGrantType.PASSWORD
+ if (!requestedClient.authorizedGrantTypes.contains(authorizedGrantType)) {
+ throw InvalidGrantException("Authorize not allowed: '$authorizedGrantType'")
+ }
+
+ val requestedIdentity = identityService.identityOf(
+ requestedClient, passwordGrantRequest.username
+ )
+
+ if (requestedIdentity == null || !identityService.validCredentials(requestedClient, requestedIdentity, passwordGrantRequest.password)) {
+ throw InvalidIdentityException()
+ }
+
+ var requestedScopes = ScopeParser.parseScopes(passwordGrantRequest.scope)
+ .toSet()
+
+ if (passwordGrantRequest.scope == null) {
+ requestedScopes = requestedClient.clientScopes
+ }
+
+ validateScopes(requestedClient, requestedIdentity, requestedScopes)
+
+ val accessToken = converters.accessTokenConverter.convertToToken(
+ requestedIdentity.username,
+ requestedClient.clientId,
+ requestedScopes,
+ converters.refreshTokenConverter.convertToToken(
+ requestedIdentity.username,
+ requestedClient.clientId,
+ requestedScopes
+ )
+ )
+
+ tokenStore.storeAccessToken(accessToken)
+
+ return accessToken.toTokenResponse()
+}
+
+fun GrantingCall.authorize(authorizationCodeRequest: AuthorizationCodeRequest): TokenResponse {
+ throwExceptionIfUnverifiedClient(authorizationCodeRequest)
+
+ if (authorizationCodeRequest.code == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("code"))
+ }
+
+ if (authorizationCodeRequest.redirectUri == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
+ }
+
+ val consumeCodeToken = tokenStore.consumeCodeToken(authorizationCodeRequest.code)
+ ?: throw InvalidGrantException()
+
+
+ if (consumeCodeToken.redirectUri != authorizationCodeRequest.redirectUri || consumeCodeToken.clientId != authorizationCodeRequest.clientId) {
+ throw InvalidGrantException()
+ }
+
+ val accessToken = converters.accessTokenConverter.convertToToken(
+ consumeCodeToken.username,
+ consumeCodeToken.clientId,
+ consumeCodeToken.scopes,
+ converters.refreshTokenConverter.convertToToken(
+ consumeCodeToken.username,
+ consumeCodeToken.clientId,
+ consumeCodeToken.scopes
+ )
+ )
+
+ tokenStore.storeAccessToken(accessToken)
+
+ return accessToken.toTokenResponse()
+}
+
+fun GrantingCall.authorize(clientCredentialsRequest: ClientCredentialsRequest): TokenResponse {
+ throwExceptionIfUnverifiedClient(clientCredentialsRequest)
+
+ val requestedClient = clientService.clientOf(clientCredentialsRequest.clientId!!) ?: throw InvalidClientException()
+
+ val scopes = clientCredentialsRequest.scope
+ ?.let { ScopeParser.parseScopes(it).toSet() }
+ ?: requestedClient.clientScopes
+
+ val accessToken = converters.accessTokenConverter.convertToToken(
+ username = null,
+ clientId = clientCredentialsRequest.clientId,
+ requestedScopes = scopes,
+ refreshToken = converters.refreshTokenConverter.convertToToken(
+ username = null,
+ clientId = clientCredentialsRequest.clientId,
+ requestedScopes = scopes
+ )
+ )
+
+ tokenStore.storeAccessToken(accessToken)
+
+ return accessToken.toTokenResponse()
+}
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterDefault.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterDefault.kt
index d3ad54c..f2bbe5a 100644
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterDefault.kt
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterDefault.kt
@@ -1,10 +1,20 @@
package nl.myndocs.oauth2.grant
+import nl.myndocs.oauth2.authenticator.IdentityScopeVerifier
+import nl.myndocs.oauth2.client.Client
+import nl.myndocs.oauth2.exception.InvalidClientException
+import nl.myndocs.oauth2.exception.InvalidGrantException
+import nl.myndocs.oauth2.exception.InvalidRequestException
+import nl.myndocs.oauth2.exception.InvalidScopeException
+import nl.myndocs.oauth2.identity.Identity
+import nl.myndocs.oauth2.identity.TokenInfo
import nl.myndocs.oauth2.request.*
+import nl.myndocs.oauth2.response.TokenResponse
+import nl.myndocs.oauth2.token.AccessToken
import nl.myndocs.oauth2.token.toMap
fun GrantingCall.grantPassword() = granter("password") {
- val tokenResponse = tokenService.authorize(
+ val tokenResponse = authorize(
PasswordGrantRequest(
callContext.formParameters["client_id"],
callContext.formParameters["client_secret"],
@@ -18,7 +28,7 @@ fun GrantingCall.grantPassword() = granter("password") {
}
fun GrantingCall.grantClientCredentials() = granter("client_credentials") {
- val tokenResponse = tokenService.authorize(ClientCredentialsRequest(
+ val tokenResponse = authorize(ClientCredentialsRequest(
callContext.formParameters["client_id"],
callContext.formParameters["client_secret"],
callContext.formParameters["scope"]
@@ -28,7 +38,7 @@ fun GrantingCall.grantClientCredentials() = granter("client_credentials") {
}
fun GrantingCall.grantRefreshToken() = granter("refresh_token") {
- val accessToken = tokenService.refresh(
+ val accessToken = refresh(
RefreshTokenRequest(
callContext.formParameters["client_id"],
callContext.formParameters["client_secret"],
@@ -40,7 +50,7 @@ fun GrantingCall.grantRefreshToken() = granter("refresh_token") {
}
fun GrantingCall.grantAuthorizationCode() = granter("authorization_code") {
- val accessToken = tokenService.authorize(
+ val accessToken = authorize(
AuthorizationCodeRequest(
callContext.formParameters["client_id"],
callContext.formParameters["client_secret"],
@@ -51,3 +61,61 @@ fun GrantingCall.grantAuthorizationCode() = granter("authorization_code") {
callContext.respondJson(accessToken.toMap())
}
+
+internal val INVALID_REQUEST_FIELD_MESSAGE = "'%s' field is missing"
+
+fun GrantingCall.validateScopes(
+ client: Client,
+ identity: Identity,
+ requestedScopes: Set,
+ identityScopeVerifier: IdentityScopeVerifier? = null) {
+ val scopesAllowed = scopesAllowed(client.clientScopes, requestedScopes)
+ if (!scopesAllowed) {
+ throw InvalidScopeException(requestedScopes.minus(client.clientScopes))
+ }
+
+ val allowedScopes = identityScopeVerifier?.allowedScopes(client, identity, requestedScopes)
+ ?: identityService.allowedScopes(client, identity, requestedScopes)
+
+ val ivalidScopes = requestedScopes.minus(allowedScopes)
+ if (ivalidScopes.isNotEmpty()) {
+ throw InvalidScopeException(ivalidScopes)
+ }
+}
+
+fun GrantingCall.tokenInfo(accessToken: String): TokenInfo {
+ val storedAccessToken = tokenStore.accessToken(accessToken) ?: throw InvalidGrantException()
+ val client = clientService.clientOf(storedAccessToken.clientId) ?: throw InvalidClientException()
+ val identity = storedAccessToken.username?.let { identityService.identityOf(client, it) }
+
+ return TokenInfo(
+ identity,
+ client,
+ storedAccessToken.scopes
+ )
+}
+
+fun GrantingCall.throwExceptionIfUnverifiedClient(clientRequest: ClientRequest) {
+ val clientId = clientRequest.clientId
+ ?: throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
+
+ val clientSecret = clientRequest.clientSecret
+ ?: throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_secret"))
+
+ val client = clientService.clientOf(clientId) ?: throw InvalidClientException()
+
+ if (!clientService.validClient(client, clientSecret)) {
+ throw InvalidClientException()
+ }
+}
+
+fun GrantingCall.scopesAllowed(clientScopes: Set, requestedScopes: Set): Boolean {
+ return clientScopes.containsAll(requestedScopes)
+}
+
+fun AccessToken.toTokenResponse() = TokenResponse(
+ accessToken,
+ tokenType,
+ expiresIn(),
+ refreshToken?.refreshToken
+)
\ No newline at end of file
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterRedirect.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterRedirect.kt
new file mode 100644
index 0000000..e6f54f1
--- /dev/null
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterRedirect.kt
@@ -0,0 +1,135 @@
+package nl.myndocs.oauth2.grant
+
+import nl.myndocs.oauth2.authenticator.Authenticator
+import nl.myndocs.oauth2.authenticator.IdentityScopeVerifier
+import nl.myndocs.oauth2.client.AuthorizedGrantType
+import nl.myndocs.oauth2.exception.InvalidClientException
+import nl.myndocs.oauth2.exception.InvalidGrantException
+import nl.myndocs.oauth2.exception.InvalidIdentityException
+import nl.myndocs.oauth2.exception.InvalidRequestException
+import nl.myndocs.oauth2.request.RedirectAuthorizationCodeRequest
+import nl.myndocs.oauth2.request.RedirectTokenRequest
+import nl.myndocs.oauth2.scope.ScopeParser
+import nl.myndocs.oauth2.token.AccessToken
+import nl.myndocs.oauth2.token.CodeToken
+
+
+fun GrantingCall.redirect(
+ redirect: RedirectAuthorizationCodeRequest,
+ authenticator: Authenticator?,
+ identityScopeVerifier: IdentityScopeVerifier?
+): CodeToken {
+ if (redirect.clientId == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
+ }
+
+ if (redirect.username == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
+ }
+
+ if (redirect.password == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
+ }
+ if (redirect.redirectUri == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
+ }
+
+ val clientOf = clientService.clientOf(redirect.clientId) ?: throw InvalidClientException()
+
+ if (!clientOf.redirectUris.contains(redirect.redirectUri)) {
+ throw InvalidGrantException("invalid 'redirect_uri'")
+ }
+
+ val authorizedGrantType = AuthorizedGrantType.AUTHORIZATION_CODE
+ if (!clientOf.authorizedGrantTypes.contains(authorizedGrantType)) {
+ throw InvalidGrantException("Authorize not allowed: '$authorizedGrantType'")
+ }
+
+ val identityOf = identityService.identityOf(clientOf, redirect.username) ?: throw InvalidIdentityException()
+
+ var validIdentity = authenticator?.validCredentials(clientOf, identityOf, redirect.password)
+ ?: identityService.validCredentials(clientOf, identityOf, redirect.password)
+
+ if (!validIdentity) {
+ throw InvalidIdentityException()
+ }
+
+ var requestedScopes = ScopeParser.parseScopes(redirect.scope)
+
+ if (redirect.scope == null) {
+ requestedScopes = clientOf.clientScopes
+ }
+
+ validateScopes(clientOf, identityOf, requestedScopes, identityScopeVerifier)
+
+ val codeToken = converters.codeTokenConverter.convertToToken(
+ identityOf.username,
+ clientOf.clientId,
+ redirect.redirectUri,
+ requestedScopes
+ )
+
+ tokenStore.storeCodeToken(codeToken)
+
+ return codeToken
+}
+
+fun GrantingCall.redirect(
+ redirect: RedirectTokenRequest,
+ authenticator: Authenticator?,
+ identityScopeVerifier: IdentityScopeVerifier?
+): AccessToken {
+ if (redirect.clientId == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
+ }
+
+ if (redirect.username == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
+ }
+
+ if (redirect.password == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
+ }
+ if (redirect.redirectUri == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
+ }
+
+ val clientOf = clientService.clientOf(redirect.clientId) ?: throw InvalidClientException()
+
+ if (!clientOf.redirectUris.contains(redirect.redirectUri)) {
+ throw InvalidGrantException("invalid 'redirect_uri'")
+ }
+
+ val authorizedGrantType = AuthorizedGrantType.IMPLICIT
+ if (!clientOf.authorizedGrantTypes.contains(authorizedGrantType)) {
+ throw InvalidGrantException("Authorize not allowed: '$authorizedGrantType'")
+ }
+
+ val identityOf = identityService.identityOf(clientOf, redirect.username) ?: throw InvalidIdentityException()
+
+ var validIdentity = authenticator?.validCredentials(clientOf, identityOf, redirect.password)
+ ?: identityService.validCredentials(clientOf, identityOf, redirect.password)
+
+ if (!validIdentity) {
+ throw InvalidIdentityException()
+ }
+
+ var requestedScopes = ScopeParser.parseScopes(redirect.scope)
+
+ if (redirect.scope == null) {
+ requestedScopes = clientOf.clientScopes
+ }
+
+ validateScopes(clientOf, identityOf, requestedScopes, identityScopeVerifier)
+
+ val accessToken = converters.accessTokenConverter.convertToToken(
+ identityOf.username,
+ clientOf.clientId,
+ requestedScopes,
+ null
+ )
+
+ tokenStore.storeAccessToken(accessToken)
+
+ return accessToken
+}
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterRefresh.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterRefresh.kt
new file mode 100644
index 0000000..5c4c456
--- /dev/null
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/CallRouterRefresh.kt
@@ -0,0 +1,41 @@
+package nl.myndocs.oauth2.grant
+
+import nl.myndocs.oauth2.client.AuthorizedGrantType
+import nl.myndocs.oauth2.exception.InvalidClientException
+import nl.myndocs.oauth2.exception.InvalidGrantException
+import nl.myndocs.oauth2.exception.InvalidRequestException
+import nl.myndocs.oauth2.request.RefreshTokenRequest
+import nl.myndocs.oauth2.response.TokenResponse
+
+
+fun GrantingCall.refresh(refreshTokenRequest: RefreshTokenRequest): TokenResponse {
+ throwExceptionIfUnverifiedClient(refreshTokenRequest)
+
+ if (refreshTokenRequest.refreshToken == null) {
+ throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("refresh_token"))
+ }
+
+ val refreshToken = tokenStore.refreshToken(refreshTokenRequest.refreshToken) ?: throw InvalidGrantException()
+
+ if (refreshToken.clientId != refreshTokenRequest.clientId) {
+ throw InvalidGrantException()
+ }
+
+ val client = clientService.clientOf(refreshToken.clientId) ?: throw InvalidClientException()
+
+ val authorizedGrantType = AuthorizedGrantType.REFRESH_TOKEN
+ if (!client.authorizedGrantTypes.contains(authorizedGrantType)) {
+ throw InvalidGrantException("Authorize not allowed: '$authorizedGrantType'")
+ }
+
+ val accessToken = converters.accessTokenConverter.convertToToken(
+ refreshToken.username,
+ refreshToken.clientId,
+ refreshToken.scopes,
+ converters.refreshTokenConverter.convertToToken(refreshToken)
+ )
+
+ tokenStore.storeAccessToken(accessToken)
+
+ return accessToken.toTokenResponse()
+}
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/GrantingCall.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/GrantingCall.kt
index 046d1d5..b219253 100644
--- a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/GrantingCall.kt
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/grant/GrantingCall.kt
@@ -1,9 +1,15 @@
package nl.myndocs.oauth2.grant
-import nl.myndocs.oauth2.TokenService
+import nl.myndocs.oauth2.client.ClientService
+import nl.myndocs.oauth2.identity.IdentityService
import nl.myndocs.oauth2.request.CallContext
+import nl.myndocs.oauth2.token.TokenStore
+import nl.myndocs.oauth2.token.converter.Converters
interface GrantingCall {
val callContext: CallContext
- val tokenService: TokenService
+ val identityService: IdentityService
+ val clientService: ClientService
+ val tokenStore: TokenStore
+ val converters: Converters
}
\ No newline at end of file
diff --git a/oauth2-server-core/src/main/java/nl/myndocs/oauth2/token/converter/Converters.kt b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/token/converter/Converters.kt
new file mode 100644
index 0000000..8e40c27
--- /dev/null
+++ b/oauth2-server-core/src/main/java/nl/myndocs/oauth2/token/converter/Converters.kt
@@ -0,0 +1,7 @@
+package nl.myndocs.oauth2.token.converter
+
+data class Converters(
+ val accessTokenConverter: AccessTokenConverter,
+ val refreshTokenConverter: RefreshTokenConverter,
+ val codeTokenConverter: CodeTokenConverter
+)
\ No newline at end of file
diff --git a/oauth2-server-core/src/test/java/nl/myndocs/oauth2/AuthorizationCodeGrantTokenServiceTest.kt b/oauth2-server-core/src/test/java/nl/myndocs/oauth2/AuthorizationCodeGrantTokenServiceTest.kt
index ef268e0..dc5cb3f 100644
--- a/oauth2-server-core/src/test/java/nl/myndocs/oauth2/AuthorizationCodeGrantTokenServiceTest.kt
+++ b/oauth2-server-core/src/test/java/nl/myndocs/oauth2/AuthorizationCodeGrantTokenServiceTest.kt
@@ -1,7 +1,6 @@
package nl.myndocs.oauth2
import io.mockk.every
-import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit5.MockKExtension
@@ -11,23 +10,30 @@ import nl.myndocs.oauth2.client.ClientService
import nl.myndocs.oauth2.exception.InvalidClientException
import nl.myndocs.oauth2.exception.InvalidGrantException
import nl.myndocs.oauth2.exception.InvalidRequestException
+import nl.myndocs.oauth2.grant.GrantingCall
+import nl.myndocs.oauth2.grant.authorize
import nl.myndocs.oauth2.identity.Identity
import nl.myndocs.oauth2.identity.IdentityService
import nl.myndocs.oauth2.request.AuthorizationCodeRequest
+import nl.myndocs.oauth2.request.CallContext
import nl.myndocs.oauth2.token.AccessToken
import nl.myndocs.oauth2.token.CodeToken
import nl.myndocs.oauth2.token.RefreshToken
import nl.myndocs.oauth2.token.TokenStore
import nl.myndocs.oauth2.token.converter.AccessTokenConverter
import nl.myndocs.oauth2.token.converter.CodeTokenConverter
+import nl.myndocs.oauth2.token.converter.Converters
import nl.myndocs.oauth2.token.converter.RefreshTokenConverter
import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.time.Instant
@ExtendWith(MockKExtension::class)
internal class AuthorizationCodeGrantTokenServiceTest {
+ @MockK
+ lateinit var callContext: CallContext
@MockK
lateinit var identityService: IdentityService
@MockK
@@ -41,8 +47,22 @@ internal class AuthorizationCodeGrantTokenServiceTest {
@MockK
lateinit var codeTokenConverter: CodeTokenConverter
- @InjectMockKs
- lateinit var tokenService: Oauth2TokenService
+ lateinit var grantingCall: GrantingCall
+
+ @BeforeEach
+ fun initialize() {
+ grantingCall = object : GrantingCall {
+ override val callContext = this@AuthorizationCodeGrantTokenServiceTest.callContext
+ override val identityService = this@AuthorizationCodeGrantTokenServiceTest.identityService
+ override val clientService = this@AuthorizationCodeGrantTokenServiceTest.clientService
+ override val tokenStore = this@AuthorizationCodeGrantTokenServiceTest.tokenStore
+ override val converters = Converters(
+ this@AuthorizationCodeGrantTokenServiceTest.accessTokenConverter,
+ this@AuthorizationCodeGrantTokenServiceTest.refreshTokenConverter,
+ this@AuthorizationCodeGrantTokenServiceTest.codeTokenConverter
+ )
+ }
+ }
val clientId = "client-foo"
val clientSecret = "client-bar"
@@ -75,7 +95,7 @@ internal class AuthorizationCodeGrantTokenServiceTest {
every { refreshTokenConverter.convertToToken(username, clientId, requestScopes) } returns refreshToken
every { accessTokenConverter.convertToToken(username, clientId, requestScopes, refreshToken) } returns accessToken
- tokenService.authorize(authorizationCodeRequest)
+ grantingCall.authorize(authorizationCodeRequest)
}
@Test
@@ -84,7 +104,7 @@ internal class AuthorizationCodeGrantTokenServiceTest {
assertThrows(
InvalidClientException::class.java
- ) { tokenService.authorize(authorizationCodeRequest) }
+ ) { grantingCall.authorize(authorizationCodeRequest) }
}
@Test
@@ -95,7 +115,7 @@ internal class AuthorizationCodeGrantTokenServiceTest {
assertThrows(
InvalidClientException::class.java
- ) { tokenService.authorize(authorizationCodeRequest) }
+ ) { grantingCall.authorize(authorizationCodeRequest) }
}
@Test
@@ -113,7 +133,7 @@ internal class AuthorizationCodeGrantTokenServiceTest {
assertThrows(
InvalidRequestException::class.java
- ) { tokenService.authorize(authorizationCodeRequest) }
+ ) { grantingCall.authorize(authorizationCodeRequest) }
}
@Test
@@ -131,7 +151,7 @@ internal class AuthorizationCodeGrantTokenServiceTest {
assertThrows(
InvalidRequestException::class.java
- ) { tokenService.authorize(authorizationCodeRequest) }
+ ) { grantingCall.authorize(authorizationCodeRequest) }
}
@Test
@@ -153,7 +173,7 @@ internal class AuthorizationCodeGrantTokenServiceTest {
assertThrows(
InvalidGrantException::class.java
- ) { tokenService.authorize(authorizationCodeRequest) }
+ ) { grantingCall.authorize(authorizationCodeRequest) }
}
@Test
@@ -166,7 +186,7 @@ internal class AuthorizationCodeGrantTokenServiceTest {
assertThrows(
InvalidGrantException::class.java
- ) { tokenService.authorize(authorizationCodeRequest) }
+ ) { grantingCall.authorize(authorizationCodeRequest) }
}
}
\ No newline at end of file
diff --git a/oauth2-server-core/src/test/java/nl/myndocs/oauth2/ClientCredentialsTokenServiceTest.kt b/oauth2-server-core/src/test/java/nl/myndocs/oauth2/ClientCredentialsTokenServiceTest.kt
index a3c1277..4675402 100644
--- a/oauth2-server-core/src/test/java/nl/myndocs/oauth2/ClientCredentialsTokenServiceTest.kt
+++ b/oauth2-server-core/src/test/java/nl/myndocs/oauth2/ClientCredentialsTokenServiceTest.kt
@@ -1,7 +1,6 @@
package nl.myndocs.oauth2
import io.mockk.every
-import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit5.MockKExtension
@@ -10,21 +9,28 @@ import nl.myndocs.oauth2.client.AuthorizedGrantType
import nl.myndocs.oauth2.client.Client
import nl.myndocs.oauth2.client.ClientService
import nl.myndocs.oauth2.exception.InvalidClientException
+import nl.myndocs.oauth2.grant.GrantingCall
+import nl.myndocs.oauth2.grant.authorize
import nl.myndocs.oauth2.identity.IdentityService
+import nl.myndocs.oauth2.request.CallContext
import nl.myndocs.oauth2.request.ClientCredentialsRequest
import nl.myndocs.oauth2.token.AccessToken
import nl.myndocs.oauth2.token.RefreshToken
import nl.myndocs.oauth2.token.TokenStore
import nl.myndocs.oauth2.token.converter.AccessTokenConverter
import nl.myndocs.oauth2.token.converter.CodeTokenConverter
+import nl.myndocs.oauth2.token.converter.Converters
import nl.myndocs.oauth2.token.converter.RefreshTokenConverter
import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.time.Instant
@ExtendWith(MockKExtension::class)
internal class ClientCredentialsTokenServiceTest {
+ @MockK
+ lateinit var callContext: CallContext
@MockK
lateinit var identityService: IdentityService
@MockK
@@ -38,9 +44,22 @@ internal class ClientCredentialsTokenServiceTest {
@MockK
lateinit var codeTokenConverter: CodeTokenConverter
- @InjectMockKs
- lateinit var tokenService: Oauth2TokenService
-
+ lateinit var grantingCall: GrantingCall
+
+ @BeforeEach
+ fun initialize() {
+ grantingCall = object : GrantingCall {
+ override val callContext = this@ClientCredentialsTokenServiceTest.callContext
+ override val identityService = this@ClientCredentialsTokenServiceTest.identityService
+ override val clientService = this@ClientCredentialsTokenServiceTest.clientService
+ override val tokenStore = this@ClientCredentialsTokenServiceTest.tokenStore
+ override val converters = Converters(
+ this@ClientCredentialsTokenServiceTest.accessTokenConverter,
+ this@ClientCredentialsTokenServiceTest.refreshTokenConverter,
+ this@ClientCredentialsTokenServiceTest.codeTokenConverter
+ )
+ }
+ }
private val clientId = "client-foo"
private val clientSecret = "client-secret"
private val scope = "scope1"
@@ -58,7 +77,7 @@ internal class ClientCredentialsTokenServiceTest {
every { refreshTokenConverter.convertToToken(null, clientId, scopes) } returns refreshToken
every { accessTokenConverter.convertToToken(null, clientId, scopes, refreshToken) } returns accessToken
- tokenService.authorize(clientCredentialsRequest)
+ grantingCall.authorize(clientCredentialsRequest)
verify { tokenStore.storeAccessToken(accessToken) }
}
@@ -69,7 +88,7 @@ internal class ClientCredentialsTokenServiceTest {
Assertions.assertThrows(
InvalidClientException::class.java
- ) { tokenService.authorize(clientCredentialsRequest) }
+ ) { grantingCall.authorize(clientCredentialsRequest) }
}
@Test
@@ -80,7 +99,7 @@ internal class ClientCredentialsTokenServiceTest {
Assertions.assertThrows(
InvalidClientException::class.java
- ) { tokenService.authorize(clientCredentialsRequest) }
+ ) { grantingCall.authorize(clientCredentialsRequest) }
}
@Test
@@ -101,6 +120,6 @@ internal class ClientCredentialsTokenServiceTest {
every { refreshTokenConverter.convertToToken(null, clientId, requestScopes) } returns refreshToken
every { accessTokenConverter.convertToToken(null, clientId, requestScopes, refreshToken) } returns accessToken
- tokenService.authorize(clientCredentialsRequest)
+ grantingCall.authorize(clientCredentialsRequest)
}
}
\ No newline at end of file
diff --git a/oauth2-server-core/src/test/java/nl/myndocs/oauth2/PasswordGrantTokenServiceTest.kt b/oauth2-server-core/src/test/java/nl/myndocs/oauth2/PasswordGrantTokenServiceTest.kt
index 9282ba5..4259549 100644
--- a/oauth2-server-core/src/test/java/nl/myndocs/oauth2/PasswordGrantTokenServiceTest.kt
+++ b/oauth2-server-core/src/test/java/nl/myndocs/oauth2/PasswordGrantTokenServiceTest.kt
@@ -1,7 +1,6 @@
package nl.myndocs.oauth2
import io.mockk.every
-import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit5.MockKExtension
@@ -13,22 +12,29 @@ import nl.myndocs.oauth2.exception.InvalidClientException
import nl.myndocs.oauth2.exception.InvalidIdentityException
import nl.myndocs.oauth2.exception.InvalidRequestException
import nl.myndocs.oauth2.exception.InvalidScopeException
+import nl.myndocs.oauth2.grant.GrantingCall
+import nl.myndocs.oauth2.grant.authorize
import nl.myndocs.oauth2.identity.Identity
import nl.myndocs.oauth2.identity.IdentityService
+import nl.myndocs.oauth2.request.CallContext
import nl.myndocs.oauth2.request.PasswordGrantRequest
import nl.myndocs.oauth2.token.AccessToken
import nl.myndocs.oauth2.token.RefreshToken
import nl.myndocs.oauth2.token.TokenStore
import nl.myndocs.oauth2.token.converter.AccessTokenConverter
import nl.myndocs.oauth2.token.converter.CodeTokenConverter
+import nl.myndocs.oauth2.token.converter.Converters
import nl.myndocs.oauth2.token.converter.RefreshTokenConverter
import org.junit.jupiter.api.Assertions.assertThrows
+import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.time.Instant
@ExtendWith(MockKExtension::class)
internal class PasswordGrantTokenServiceTest {
+ @MockK
+ lateinit var callContext: CallContext
@MockK
lateinit var identityService: IdentityService
@MockK
@@ -42,9 +48,22 @@ internal class PasswordGrantTokenServiceTest {
@MockK
lateinit var codeTokenConverter: CodeTokenConverter
- @InjectMockKs
- lateinit var tokenService: Oauth2TokenService
-
+ lateinit var grantingCall: GrantingCall
+
+ @BeforeEach
+ fun initialize() {
+ grantingCall = object : GrantingCall {
+ override val callContext = this@PasswordGrantTokenServiceTest.callContext
+ override val identityService = this@PasswordGrantTokenServiceTest.identityService
+ override val clientService = this@PasswordGrantTokenServiceTest.clientService
+ override val tokenStore = this@PasswordGrantTokenServiceTest.tokenStore
+ override val converters = Converters(
+ this@PasswordGrantTokenServiceTest.accessTokenConverter,
+ this@PasswordGrantTokenServiceTest.refreshTokenConverter,
+ this@PasswordGrantTokenServiceTest.codeTokenConverter
+ )
+ }
+ }
val clientId = "client-foo"
val clientSecret = "client-bar"
val username = "user-foo"
@@ -76,7 +95,7 @@ internal class PasswordGrantTokenServiceTest {
every { refreshTokenConverter.convertToToken(username, clientId, requestScopes) } returns refreshToken
every { accessTokenConverter.convertToToken(username, clientId, requestScopes, refreshToken) } returns accessToken
- tokenService.authorize(passwordGrantRequest)
+ grantingCall.authorize(passwordGrantRequest)
verify { tokenStore.storeAccessToken(accessToken) }
}
@@ -87,7 +106,7 @@ internal class PasswordGrantTokenServiceTest {
assertThrows(
InvalidClientException::class.java
- ) { tokenService.authorize(passwordGrantRequest) }
+ ) { grantingCall.authorize(passwordGrantRequest) }
}
@Test
@@ -98,7 +117,7 @@ internal class PasswordGrantTokenServiceTest {
assertThrows(
InvalidClientException::class.java
- ) { tokenService.authorize(passwordGrantRequest) }
+ ) { grantingCall.authorize(passwordGrantRequest) }
}
@Test
@@ -117,7 +136,7 @@ internal class PasswordGrantTokenServiceTest {
assertThrows(
InvalidRequestException::class.java
- ) { tokenService.authorize(passwordGrantRequest) }
+ ) { grantingCall.authorize(passwordGrantRequest) }
}
@Test
@@ -136,7 +155,7 @@ internal class PasswordGrantTokenServiceTest {
assertThrows(
InvalidRequestException::class.java
- ) { tokenService.authorize(passwordGrantRequest) }
+ ) { grantingCall.authorize(passwordGrantRequest) }
}
@Test
@@ -151,7 +170,7 @@ internal class PasswordGrantTokenServiceTest {
assertThrows(
InvalidIdentityException::class.java
- ) { tokenService.authorize(passwordGrantRequest) }
+ ) { grantingCall.authorize(passwordGrantRequest) }
}
@Test
@@ -167,7 +186,7 @@ internal class PasswordGrantTokenServiceTest {
assertThrows(
InvalidScopeException::class.java
- ) { tokenService.authorize(passwordGrantRequest) }
+ ) { grantingCall.authorize(passwordGrantRequest) }
}
@Test
@@ -183,7 +202,7 @@ internal class PasswordGrantTokenServiceTest {
assertThrows(
InvalidScopeException::class.java
- ) { tokenService.authorize(passwordGrantRequest) }
+ ) { grantingCall.authorize(passwordGrantRequest) }
}
@Test
@@ -210,6 +229,6 @@ internal class PasswordGrantTokenServiceTest {
every { refreshTokenConverter.convertToToken(username, clientId, requestScopes) } returns refreshToken
every { accessTokenConverter.convertToToken(username, clientId, requestScopes, refreshToken) } returns accessToken
- tokenService.authorize(passwordGrantRequest)
+ grantingCall.authorize(passwordGrantRequest)
}
}
\ No newline at end of file
diff --git a/oauth2-server-core/src/test/java/nl/myndocs/oauth2/RefreshTokenGrantTokenServiceTest.kt b/oauth2-server-core/src/test/java/nl/myndocs/oauth2/RefreshTokenGrantTokenServiceTest.kt
index 96a428c..f8ec5d9 100644
--- a/oauth2-server-core/src/test/java/nl/myndocs/oauth2/RefreshTokenGrantTokenServiceTest.kt
+++ b/oauth2-server-core/src/test/java/nl/myndocs/oauth2/RefreshTokenGrantTokenServiceTest.kt
@@ -1,7 +1,6 @@
package nl.myndocs.oauth2
import io.mockk.every
-import io.mockk.impl.annotations.InjectMockKs
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.junit5.MockKExtension
@@ -12,22 +11,29 @@ import nl.myndocs.oauth2.client.ClientService
import nl.myndocs.oauth2.exception.InvalidClientException
import nl.myndocs.oauth2.exception.InvalidGrantException
import nl.myndocs.oauth2.exception.InvalidRequestException
+import nl.myndocs.oauth2.grant.GrantingCall
+import nl.myndocs.oauth2.grant.refresh
import nl.myndocs.oauth2.identity.Identity
import nl.myndocs.oauth2.identity.IdentityService
+import nl.myndocs.oauth2.request.CallContext
import nl.myndocs.oauth2.request.RefreshTokenRequest
import nl.myndocs.oauth2.token.AccessToken
import nl.myndocs.oauth2.token.RefreshToken
import nl.myndocs.oauth2.token.TokenStore
import nl.myndocs.oauth2.token.converter.AccessTokenConverter
import nl.myndocs.oauth2.token.converter.CodeTokenConverter
+import nl.myndocs.oauth2.token.converter.Converters
import nl.myndocs.oauth2.token.converter.RefreshTokenConverter
import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.time.Instant
@ExtendWith(MockKExtension::class)
internal class RefreshTokenGrantTokenServiceTest {
+ @MockK
+ lateinit var callContext: CallContext
@MockK
lateinit var identityService: IdentityService
@MockK
@@ -41,9 +47,22 @@ internal class RefreshTokenGrantTokenServiceTest {
@MockK
lateinit var codeTokenConverter: CodeTokenConverter
- @InjectMockKs
- lateinit var tokenService: Oauth2TokenService
-
+ lateinit var grantingCall: GrantingCall
+
+ @BeforeEach
+ fun initialize() {
+ grantingCall = object : GrantingCall {
+ override val callContext = this@RefreshTokenGrantTokenServiceTest.callContext
+ override val identityService = this@RefreshTokenGrantTokenServiceTest.identityService
+ override val clientService = this@RefreshTokenGrantTokenServiceTest.clientService
+ override val tokenStore = this@RefreshTokenGrantTokenServiceTest.tokenStore
+ override val converters = Converters(
+ this@RefreshTokenGrantTokenServiceTest.accessTokenConverter,
+ this@RefreshTokenGrantTokenServiceTest.refreshTokenConverter,
+ this@RefreshTokenGrantTokenServiceTest.codeTokenConverter
+ )
+ }
+ }
val clientId = "client-foo"
val clientSecret = "client-bar"
val refreshToken = "refresh-token"
@@ -72,7 +91,7 @@ internal class RefreshTokenGrantTokenServiceTest {
every { refreshTokenConverter.convertToToken(token) } returns newRefreshToken
every { accessTokenConverter.convertToToken(username, clientId, scopes, newRefreshToken) } returns accessToken
- tokenService.refresh(refreshTokenRequest)
+ grantingCall.refresh(refreshTokenRequest)
verify { tokenStore.storeAccessToken(accessToken) }
@@ -93,7 +112,7 @@ internal class RefreshTokenGrantTokenServiceTest {
Assertions.assertThrows(
InvalidRequestException::class.java
- ) { tokenService.refresh(refreshTokenRequest) }
+ ) { grantingCall.refresh(refreshTokenRequest) }
}
@Test
@@ -102,7 +121,7 @@ internal class RefreshTokenGrantTokenServiceTest {
Assertions.assertThrows(
InvalidClientException::class.java
- ) { tokenService.refresh(refreshTokenRequest) }
+ ) { grantingCall.refresh(refreshTokenRequest) }
}
@Test
@@ -113,7 +132,7 @@ internal class RefreshTokenGrantTokenServiceTest {
Assertions.assertThrows(
InvalidClientException::class.java
- ) { tokenService.refresh(refreshTokenRequest) }
+ ) { grantingCall.refresh(refreshTokenRequest) }
}
@Test
@@ -127,6 +146,6 @@ internal class RefreshTokenGrantTokenServiceTest {
Assertions.assertThrows(
InvalidGrantException::class.java
- ) { tokenService.refresh(refreshTokenRequest) }
+ ) { grantingCall.refresh(refreshTokenRequest) }
}
}
\ No newline at end of file