diff --git a/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt index fb9bf9d..7a109ba 100644 --- a/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt +++ b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/CallRouter.kt @@ -8,9 +8,9 @@ import nl.myndocs.oauth2.token.toMap class CallRouter( private val tokenService: TokenService, - private val tokenEndpoint: String, - private val authorizeEndpoint: String, - private val userinfoEndpoint: String, + val tokenEndpoint: String, + val authorizeEndpoint: String, + val userInfoEndpoint: String, private val userInfoCallback: (UserInfo) -> Map ) { companion object { @@ -28,7 +28,7 @@ class CallRouter( when (callContext.path) { tokenEndpoint -> routeTokenEndpoint(callContext) authorizeEndpoint -> routeAuthorizeEndpoint(callContext, authorizer) - userinfoEndpoint -> routeUserInfoEndpoint(callContext) + userInfoEndpoint -> routeUserInfoEndpoint(callContext) } } diff --git a/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/Oauth2TokenService.kt b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/Oauth2TokenService.kt new file mode 100644 index 0000000..277eb15 --- /dev/null +++ b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/Oauth2TokenService.kt @@ -0,0 +1,313 @@ +package nl.myndocs.oauth2 + +import nl.myndocs.oauth2.authenticator.Authenticator +import nl.myndocs.oauth2.authenticator.IdentityScopeVerifier +import nl.myndocs.oauth2.client.ClientService +import nl.myndocs.oauth2.exception.* +import nl.myndocs.oauth2.identity.IdentityService +import nl.myndocs.oauth2.identity.UserInfo +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!! + )!! + 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 + } + + val scopesAllowed = scopesAllowed(requestedClient.clientScopes, requestedScopes) + + if (!scopesAllowed) { + throw InvalidScopeException(requestedScopes.minus(requestedClient.clientScopes)) + } + + if (!identityService.validScopes(requestedClient, requestedIdentity, requestedScopes)) { + throw InvalidScopeException(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 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 accessToken = accessTokenConverter.convertToToken( + refreshToken.username, + refreshToken.clientId, + refreshToken.scopes, + 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 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 + } + + val scopesAllowed = identityScopeVerifier?.validScopes(clientOf, identityOf, requestedScopes) + ?: scopesAllowed(clientOf.clientScopes, requestedScopes) + + if (!scopesAllowed) { + throw InvalidScopeException(requestedScopes.minus(clientOf.clientScopes)) + } + + if (!identityService.validScopes(clientOf, identityOf, requestedScopes)) { + throw InvalidScopeException(requestedScopes) + } + + 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 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 + } + + val scopesAllowed = identityScopeVerifier?.validScopes(clientOf, identityOf, requestedScopes) + ?: scopesAllowed(clientOf.clientScopes, requestedScopes) + if (!scopesAllowed) { + throw InvalidScopeException(requestedScopes.minus(clientOf.clientScopes)) + } + + if (!identityService.validScopes(clientOf, identityOf, requestedScopes)) { + throw InvalidScopeException(requestedScopes) + } + + val accessToken = accessTokenConverter.convertToToken( + identityOf.username, + clientOf.clientId, + requestedScopes, + null + ) + + tokenStore.storeAccessToken(accessToken) + + return accessToken + } + + override fun userInfo(accessToken: String): UserInfo { + val storedAccessToken = tokenStore.accessToken(accessToken)!! + val client = clientService.clientOf(storedAccessToken.clientId)!! + val identity = identityService.identityOf(client, storedAccessToken.username)!! + + return UserInfo( + identity, + client, + storedAccessToken.scopes + ) + } + + private fun throwExceptionIfUnverifiedClient(clientRequest: ClientRequest) { + if (clientRequest.clientId == null) { + throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id")) + } + + if (clientRequest.clientSecret == null) { + throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_secret")) + } + + val client = clientService.clientOf(clientRequest.clientId!!) ?: throw InvalidClientException() + + if (!clientService.validClient(client, clientRequest.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/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/TokenService.kt b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/TokenService.kt index 4a3f2f1..80c9e78 100644 --- a/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/TokenService.kt +++ b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/TokenService.kt @@ -2,312 +2,30 @@ package nl.myndocs.oauth2 import nl.myndocs.oauth2.authenticator.Authenticator import nl.myndocs.oauth2.authenticator.IdentityScopeVerifier -import nl.myndocs.oauth2.client.ClientService -import nl.myndocs.oauth2.exception.* -import nl.myndocs.oauth2.identity.IdentityService import nl.myndocs.oauth2.identity.UserInfo 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 TokenService( - private val identityService: IdentityService, - private val clientService: ClientService, - private val tokenStore: TokenStore, - private val accessTokenConverter: AccessTokenConverter, - private val refreshTokenConverter: RefreshTokenConverter, - private val codeTokenConverter: CodeTokenConverter -) { - private val INVALID_REQUEST_FIELD_MESSAGE = "'%s' field is missing" - /** - * @throws InvalidIdentityException - * @throws InvalidClientException - * @throws InvalidScopeException - */ - fun authorize(passwordGrantRequest: PasswordGrantRequest): TokenResponse { - throwExceptionIfUnverifiedClient(passwordGrantRequest) +interface TokenService { + fun authorize(passwordGrantRequest: PasswordGrantRequest): TokenResponse - if (passwordGrantRequest.username == null) { - throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username")) - } + fun authorize(authorizationCodeRequest: AuthorizationCodeRequest): TokenResponse - if (passwordGrantRequest.password == null) { - throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password")) - } - - val requestedClient = clientService.clientOf( - passwordGrantRequest.clientId!! - )!! - 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 - } - - val scopesAllowed = scopesAllowed(requestedClient.clientScopes, requestedScopes) - - if (!scopesAllowed) { - throw InvalidScopeException(requestedScopes.minus(requestedClient.clientScopes)) - } - - if (!identityService.validScopes(requestedClient, requestedIdentity, requestedScopes)) { - throw InvalidScopeException(requestedScopes) - } - - val accessToken = accessTokenConverter.convertToToken( - requestedIdentity.username, - requestedClient.clientId, - requestedScopes, - refreshTokenConverter.convertToToken( - requestedIdentity.username, - requestedClient.clientId, - requestedScopes - ) - ) - - tokenStore.storeAccessToken(accessToken) - - return accessToken.toTokenResponse() - } - - 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() - } - - 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 accessToken = accessTokenConverter.convertToToken( - refreshToken.username, - refreshToken.clientId, - refreshToken.scopes, - refreshToken - ) - - tokenStore.storeAccessToken(accessToken) - - return accessToken.toTokenResponse() - } + fun refresh(refreshTokenRequest: RefreshTokenRequest): TokenResponse 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 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 - } - - val scopesAllowed = identityScopeVerifier?.validScopes(clientOf, identityOf, requestedScopes) - ?: scopesAllowed(clientOf.clientScopes, requestedScopes) - - if (!scopesAllowed) { - throw InvalidScopeException(requestedScopes.minus(clientOf.clientScopes)) - } - - if (!identityService.validScopes(clientOf, identityOf, requestedScopes)) { - throw InvalidScopeException(requestedScopes) - } - - val codeToken = codeTokenConverter.convertToToken( - identityOf.username, - clientOf.clientId, - redirect.redirectUri, - requestedScopes - ) - - tokenStore.storeCodeToken(codeToken) - - return codeToken - } + ): CodeToken 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 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 - } - - val scopesAllowed = identityScopeVerifier?.validScopes(clientOf, identityOf, requestedScopes) - ?: scopesAllowed(clientOf.clientScopes, requestedScopes) - if (!scopesAllowed) { - throw InvalidScopeException(requestedScopes.minus(clientOf.clientScopes)) - } - - if (!identityService.validScopes(clientOf, identityOf, requestedScopes)) { - throw InvalidScopeException(requestedScopes) - } - - val accessToken = accessTokenConverter.convertToToken( - identityOf.username, - clientOf.clientId, - requestedScopes, - null - ) - - tokenStore.storeAccessToken(accessToken) - - return accessToken - } - - fun userInfo(accessToken: String): UserInfo { - val storedAccessToken = tokenStore.accessToken(accessToken)!! - val client = clientService.clientOf(storedAccessToken.clientId)!! - val identity = identityService.identityOf(client, storedAccessToken.username)!! - - return UserInfo( - identity, - client, - storedAccessToken.scopes - ) - } - - private fun throwExceptionIfUnverifiedClient(clientRequest: ClientRequest) { - if (clientRequest.clientId == null) { - throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id")) - } - - if (clientRequest.clientSecret == null) { - throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_secret")) - } - - val client = clientService.clientOf(clientRequest.clientId!!) ?: throw InvalidClientException() - - if (!clientService.validClient(client, clientRequest.clientSecret!!)) { - throw InvalidClientException() - } - } - - private fun scopesAllowed(clientScopes: Set, requestedScopes: Set): Boolean { - return clientScopes.containsAll(requestedScopes) - } + ): AccessToken - private fun AccessToken.toTokenResponse() = TokenResponse( - accessToken, - tokenType, - expiresIn(), - refreshToken?.refreshToken - ) + fun userInfo(accessToken: String): UserInfo } \ No newline at end of file diff --git a/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/CallRouterBuilder.kt b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/CallRouterBuilder.kt new file mode 100644 index 0000000..f31aec1 --- /dev/null +++ b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/CallRouterBuilder.kt @@ -0,0 +1,35 @@ +package nl.myndocs.oauth2.config + +import nl.myndocs.oauth2.CallRouter +import nl.myndocs.oauth2.TokenService +import nl.myndocs.oauth2.identity.UserInfo + +internal object CallRouterBuilder { + class Configuration { + var tokenEndpoint: String = "/oauth/token" + var authorizeEndpoint: String = "/oauth/authorize" + var userInfoEndpoint: String = "/oauth/userinfo" + var userInfoCallback: (UserInfo) -> Map = { userInfo -> + mapOf( + "username" to userInfo.identity.username, + "scopes" to userInfo.scopes + ) + } + var tokenService: TokenService? = null + } + + fun build(configurer: Configuration.() -> Unit): CallRouter { + val configuration = Configuration() + configurer(configuration) + + return build(configuration) + } + + fun build(configuration: Configuration) = CallRouter( + configuration.tokenService!!, + configuration.tokenEndpoint, + configuration.authorizeEndpoint, + configuration.userInfoEndpoint, + configuration.userInfoCallback + ) +} \ No newline at end of file diff --git a/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Configuration.kt b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Configuration.kt new file mode 100644 index 0000000..90b8a19 --- /dev/null +++ b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Configuration.kt @@ -0,0 +1,12 @@ +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/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/ConfigurationBuilder.kt b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/ConfigurationBuilder.kt new file mode 100644 index 0000000..6acd614 --- /dev/null +++ b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/ConfigurationBuilder.kt @@ -0,0 +1,56 @@ +package nl.myndocs.oauth2.config + +import nl.myndocs.oauth2.TokenService +import nl.myndocs.oauth2.authenticator.Authorizer +import nl.myndocs.oauth2.identity.UserInfo +import nl.myndocs.oauth2.request.CallContext +import nl.myndocs.oauth2.request.auth.BasicAuthorizer + +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) { + callRouterConfiguration.authorizeEndpoint = value + } + + var tokenEndpoint: String + get() = callRouterConfiguration.tokenEndpoint + set(value) { + callRouterConfiguration.tokenEndpoint = value + } + + var userInfoEndpoint: String + get() = callRouterConfiguration.userInfoEndpoint + set(value) { + callRouterConfiguration.userInfoEndpoint = value + } + + var userInfoCallback: (UserInfo) -> Map + get() = callRouterConfiguration.userInfoCallback + set(value) { + callRouterConfiguration.userInfoCallback = value + } + + var authorizerFactory: (CallContext) -> Authorizer = ::BasicAuthorizer + } + + fun build(configurer: Configuration.() -> Unit): nl.myndocs.oauth2.config.Configuration { + val configuration = Configuration() + configurer(configuration) + + return nl.myndocs.oauth2.config.Configuration( + configuration.tokenService!!, + CallRouterBuilder.build(configuration.callRouterConfiguration), + configuration.authorizerFactory + ) + } +} \ No newline at end of file diff --git a/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Oauth2TokenServiceBuilder.kt b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Oauth2TokenServiceBuilder.kt new file mode 100644 index 0000000..7fbc9a3 --- /dev/null +++ b/kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/config/Oauth2TokenServiceBuilder.kt @@ -0,0 +1,32 @@ +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/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/AuthorizationCodeGrantTokenServiceTest.kt b/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/AuthorizationCodeGrantTokenServiceTest.kt index b9f32b1..fc215c2 100644 --- a/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/AuthorizationCodeGrantTokenServiceTest.kt +++ b/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/AuthorizationCodeGrantTokenServiceTest.kt @@ -41,7 +41,7 @@ internal class AuthorizationCodeGrantTokenServiceTest { lateinit var codeTokenConverter: CodeTokenConverter @InjectMockKs - lateinit var tokenService: TokenService + lateinit var tokenService: Oauth2TokenService val clientId = "client-foo" val clientSecret = "client-bar" diff --git a/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/PasswordGrantTokenServiceTest.kt b/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/PasswordGrantTokenServiceTest.kt index be5b86d..3022264 100644 --- a/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/PasswordGrantTokenServiceTest.kt +++ b/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/PasswordGrantTokenServiceTest.kt @@ -42,7 +42,7 @@ internal class PasswordGrantTokenServiceTest { lateinit var codeTokenConverter: CodeTokenConverter @InjectMockKs - lateinit var tokenService: TokenService + lateinit var tokenService: Oauth2TokenService val clientId = "client-foo" val clientSecret = "client-bar" diff --git a/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/RefreshTokenGrantTokenServiceTest.kt b/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/RefreshTokenGrantTokenServiceTest.kt index 96800e6..beef9f1 100644 --- a/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/RefreshTokenGrantTokenServiceTest.kt +++ b/kotlin-oauth2-server-core/src/test/java/nl/myndocs/oauth2/RefreshTokenGrantTokenServiceTest.kt @@ -41,7 +41,7 @@ internal class RefreshTokenGrantTokenServiceTest { lateinit var codeTokenConverter: CodeTokenConverter @InjectMockKs - lateinit var tokenService: TokenService + lateinit var tokenService: Oauth2TokenService val clientId = "client-foo" val clientSecret = "client-bar" diff --git a/kotlin-oauth2-server-javalin/src/main/java/nl/myndocs/oauth2/javalin/Oauth2Server.kt b/kotlin-oauth2-server-javalin/src/main/java/nl/myndocs/oauth2/javalin/Oauth2Server.kt index 954bcb5..2cc0840 100644 --- a/kotlin-oauth2-server-javalin/src/main/java/nl/myndocs/oauth2/javalin/Oauth2Server.kt +++ b/kotlin-oauth2-server-javalin/src/main/java/nl/myndocs/oauth2/javalin/Oauth2Server.kt @@ -2,74 +2,31 @@ package nl.myndocs.oauth2.javalin import io.javalin.Javalin import io.javalin.apibuilder.ApiBuilder.* -import nl.myndocs.oauth2.CallRouter -import nl.myndocs.oauth2.TokenService -import nl.myndocs.oauth2.authenticator.Authorizer -import nl.myndocs.oauth2.client.ClientService -import nl.myndocs.oauth2.identity.IdentityService -import nl.myndocs.oauth2.identity.UserInfo +import nl.myndocs.oauth2.config.ConfigurationBuilder import nl.myndocs.oauth2.javalin.request.JavalinCallContext -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.* -data class OauthConfiguration( - var identityService: IdentityService? = null, - var clientService: ClientService? = null, - var tokenStore: TokenStore? = null, - var tokenEndpoint: String = "/oauth/token", - var authorizeEndpoint: String = "/oauth/authorize", - var userInfoEndpoint: String = "/oauth/userinfo", - var accessTokenConverter: AccessTokenConverter = UUIDAccessTokenConverter(), - var refreshTokenConverter: RefreshTokenConverter = UUIDRefreshTokenConverter(), - var codeTokenConverter: CodeTokenConverter = UUIDCodeTokenConverter(), - var userInfoCallback: (UserInfo) -> Map = { userInfo -> - mapOf( - "username" to userInfo.identity.username, - "scopes" to userInfo.scopes - ) - }, - var authorizerFactory: (CallContext) -> Authorizer = ::BasicAuthorizer -) -fun Javalin.enableOauthServer(configurationCallback: OauthConfiguration.() -> Unit) { - val configuration = OauthConfiguration() - configuration.configurationCallback() +fun Javalin.enableOauthServer(configurationCallback: ConfigurationBuilder.Configuration.() -> Unit) { + val configuration = ConfigurationBuilder.build(configurationCallback) - val tokenService = TokenService( - configuration.identityService!!, - configuration.clientService!!, - configuration.tokenStore!!, - configuration.accessTokenConverter, - configuration.refreshTokenConverter, - configuration.codeTokenConverter - ) - - val callRouter = CallRouter( - tokenService, - configuration.tokenEndpoint, - configuration.authorizeEndpoint, - configuration.userInfoEndpoint, - configuration.userInfoCallback - ) + val callRouter = configuration.callRouter this.routes { - path(configuration.tokenEndpoint) { + path(callRouter.tokenEndpoint) { post { ctx -> val javalinCallContext = JavalinCallContext(ctx) callRouter.route(javalinCallContext, configuration.authorizerFactory(javalinCallContext)) } } - path(configuration.authorizeEndpoint) { + path(callRouter.authorizeEndpoint) { get { ctx -> val javalinCallContext = JavalinCallContext(ctx) callRouter.route(javalinCallContext, configuration.authorizerFactory(javalinCallContext)) } } - path(configuration.userInfoEndpoint) { + path(callRouter.userInfoEndpoint) { get { ctx -> val javalinCallContext = JavalinCallContext(ctx) callRouter.route(javalinCallContext, configuration.authorizerFactory(javalinCallContext)) diff --git a/kotlin-oauth2-server-ktor/src/main/java/nl/myndocs/oauth2/ktor/feature/Oauth2ServerFeature.kt b/kotlin-oauth2-server-ktor/src/main/java/nl/myndocs/oauth2/ktor/feature/Oauth2ServerFeature.kt index 350cc86..4392f09 100644 --- a/kotlin-oauth2-server-ktor/src/main/java/nl/myndocs/oauth2/ktor/feature/Oauth2ServerFeature.kt +++ b/kotlin-oauth2-server-ktor/src/main/java/nl/myndocs/oauth2/ktor/feature/Oauth2ServerFeature.kt @@ -4,71 +4,23 @@ import io.ktor.application.ApplicationCallPipeline import io.ktor.application.ApplicationFeature import io.ktor.application.call import io.ktor.util.AttributeKey -import nl.myndocs.oauth2.CallRouter -import nl.myndocs.oauth2.TokenService import nl.myndocs.oauth2.authenticator.Authorizer -import nl.myndocs.oauth2.client.ClientService -import nl.myndocs.oauth2.identity.IdentityService -import nl.myndocs.oauth2.identity.UserInfo +import nl.myndocs.oauth2.config.Configuration +import nl.myndocs.oauth2.config.ConfigurationBuilder import nl.myndocs.oauth2.ktor.feature.request.KtorCallContext 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.* class Oauth2ServerFeature(configuration: Configuration) { - val tokenEndpoint = configuration.tokenEndpoint - val authorizeEndpoint = configuration.authorizeEndpoint - val clientService = configuration.clientService!! - val identityService = configuration.identityService!! - val tokenStore = configuration.tokenStore!! - val accessTokenConverter = configuration.accessTokenConverter - val refreshTokenConverter = configuration.refreshTokenConverter - val codeTokenConverter = configuration.codeTokenConverter - val userInfoEndpoint = configuration.userInfoEndpoint - val userInfoCallback = configuration.userInfoCallback - val tokenService = TokenService( - identityService, - clientService, - tokenStore, - accessTokenConverter, - refreshTokenConverter, - codeTokenConverter - ) - val callRouter = CallRouter( - tokenService, - tokenEndpoint, - authorizeEndpoint, - userInfoEndpoint, - userInfoCallback - ) + val callRouter = configuration.callRouter + val authorizerFactory: (CallContext) -> Authorizer = configuration.authorizerFactory - class Configuration { - var tokenEndpoint = "/oauth/token" - var authorizeEndpoint = "/oauth/authorize" - var userInfoEndpoint = "/oauth/userinfo" - var clientService: ClientService? = null - var identityService: IdentityService? = null - var tokenStore: TokenStore? = null - var accessTokenConverter: AccessTokenConverter = UUIDAccessTokenConverter() - val refreshTokenConverter = UUIDRefreshTokenConverter() - var codeTokenConverter: CodeTokenConverter = UUIDCodeTokenConverter() - var userInfoCallback: (UserInfo) -> Map = { userInfo -> - mapOf( - "username" to userInfo.identity.username, - "scopes" to userInfo.scopes - ) - } - var authorizerFactory: (CallContext) -> Authorizer = ::BasicAuthorizer - } - companion object Feature : ApplicationFeature { + companion object Feature : ApplicationFeature { override val key = AttributeKey("Oauth2ServerFeature") - override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): Oauth2ServerFeature { - - val configuration = Oauth2ServerFeature.Configuration().apply(configure) + override fun install(pipeline: ApplicationCallPipeline, configure: ConfigurationBuilder.Configuration.() -> Unit): Oauth2ServerFeature { + val configuration = ConfigurationBuilder.build(configure) val feature = Oauth2ServerFeature(configuration)