Skip to content

Commit 223ce15

Browse files
authored
Merge pull request #30 from tryflux/client_credentials
Add support for client credentials grant
2 parents 2e4dd14 + f5f4902 commit 223ce15

File tree

12 files changed

+167
-8
lines changed

12 files changed

+167
-8
lines changed

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package nl.myndocs.oauth2
22

33
import nl.myndocs.oauth2.authenticator.Authorizer
4+
import nl.myndocs.oauth2.client.AuthorizedGrantType.AUTHORIZATION_CODE
5+
import nl.myndocs.oauth2.client.AuthorizedGrantType.CLIENT_CREDENTIALS
6+
import nl.myndocs.oauth2.client.AuthorizedGrantType.PASSWORD
7+
import nl.myndocs.oauth2.client.AuthorizedGrantType.REFRESH_TOKEN
48
import nl.myndocs.oauth2.exception.*
59
import nl.myndocs.oauth2.identity.UserInfo
610
import nl.myndocs.oauth2.request.*
@@ -38,7 +42,7 @@ class CallRouter(
3842
}
3943

4044
try {
41-
val allowedGrantTypes = setOf("password", "authorization_code", "refresh_token")
45+
val allowedGrantTypes = setOf(PASSWORD, AUTHORIZATION_CODE, REFRESH_TOKEN, CLIENT_CREDENTIALS)
4246
val grantType = callContext.formParameters["grant_type"]
4347
?: throw InvalidRequestException("'grant_type' not given")
4448

@@ -50,6 +54,7 @@ class CallRouter(
5054
"password" -> routePasswordGrant(callContext, tokenService)
5155
"authorization_code" -> routeAuthorizationCodeGrant(callContext, tokenService)
5256
"refresh_token" -> routeRefreshTokenGrant(callContext, tokenService)
57+
"client_credentials" -> routeClientCredentialsGrant(callContext, tokenService)
5358
}
5459
} catch (oauthException: OauthException) {
5560
callContext.respondStatus(STATUS_BAD_REQUEST)
@@ -71,6 +76,16 @@ class CallRouter(
7176
callContext.respondJson(tokenResponse.toMap())
7277
}
7378

79+
fun routeClientCredentialsGrant(callContext: CallContext, tokenService: TokenService) {
80+
val tokenResponse = tokenService.authorize(ClientCredentialsRequest(
81+
callContext.formParameters["client_id"],
82+
callContext.formParameters["client_secret"],
83+
callContext.formParameters["scope"]
84+
))
85+
86+
callContext.respondJson(tokenResponse.toMap())
87+
}
88+
7489
fun routeRefreshTokenGrant(callContext: CallContext, tokenService: TokenService) {
7590
val accessToken = tokenService.refresh(
7691
RefreshTokenRequest(

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,31 @@ class Oauth2TokenService(
119119
return accessToken.toTokenResponse()
120120
}
121121

122+
override fun authorize(clientCredentialsRequest: ClientCredentialsRequest): TokenResponse {
123+
throwExceptionIfUnverifiedClient(clientCredentialsRequest)
124+
125+
val requestedClient = clientService.clientOf(clientCredentialsRequest.clientId!!) ?: throw InvalidClientException()
126+
127+
val scopes = clientCredentialsRequest.scope
128+
?.let { ScopeParser.parseScopes(it).toSet() }
129+
?: requestedClient.clientScopes
130+
131+
val accessToken = accessTokenConverter.convertToToken(
132+
username = null,
133+
clientId = clientCredentialsRequest.clientId,
134+
requestedScopes = scopes,
135+
refreshToken = refreshTokenConverter.convertToToken(
136+
username = null,
137+
clientId = clientCredentialsRequest.clientId,
138+
requestedScopes = scopes
139+
)
140+
)
141+
142+
tokenStore.storeAccessToken(accessToken)
143+
144+
return accessToken.toTokenResponse()
145+
}
146+
122147
override fun refresh(refreshTokenRequest: RefreshTokenRequest): TokenResponse {
123148
throwExceptionIfUnverifiedClient(refreshTokenRequest)
124149

@@ -293,7 +318,7 @@ class Oauth2TokenService(
293318
override fun userInfo(accessToken: String): UserInfo {
294319
val storedAccessToken = tokenStore.accessToken(accessToken) ?: throw InvalidGrantException()
295320
val client = clientService.clientOf(storedAccessToken.clientId) ?: throw InvalidClientException()
296-
val identity = identityService.identityOf(client, storedAccessToken.username)
321+
val identity = storedAccessToken.username?.let { identityService.identityOf(client, it) }
297322
?: throw InvalidIdentityException()
298323

299324
return UserInfo(

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ interface TokenService {
1313

1414
fun authorize(authorizationCodeRequest: AuthorizationCodeRequest): TokenResponse
1515

16+
fun authorize(clientCredentialsRequest: ClientCredentialsRequest): TokenResponse
17+
1618
fun refresh(refreshTokenRequest: RefreshTokenRequest): TokenResponse
1719

1820
fun redirect(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ object AuthorizedGrantType {
55
const val REFRESH_TOKEN = "refresh_token"
66
const val PASSWORD = "password"
77
const val AUTHORIZATION_CODE = "authorization_code"
8+
const val CLIENT_CREDENTIALS = "client_credentials"
89
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package nl.myndocs.oauth2.request
2+
3+
data class ClientCredentialsRequest(
4+
override val clientId: String?,
5+
override val clientSecret: String?,
6+
val scope: String?
7+
) : ClientRequest{
8+
val grant_type = "client_credentials"
9+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ data class AccessToken(
66
val accessToken: String,
77
val tokenType: String,
88
override val expireTime: Instant,
9-
val username: String,
9+
val username: String?,
1010
val clientId: String,
1111
val scopes: Set<String>,
1212
val refreshToken: RefreshToken?

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import java.time.Instant
55
data class RefreshToken(
66
val refreshToken: String,
77
override val expireTime: Instant,
8-
val username: String,
8+
val username: String?,
99
val clientId: String,
1010
val scopes: Set<String>
1111
) : ExpirableToken

oauth2-server-core/src/main/java/nl/myndocs/oauth2/token/converter/AccessTokenConverter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import nl.myndocs.oauth2.token.AccessToken
44
import nl.myndocs.oauth2.token.RefreshToken
55

66
interface AccessTokenConverter {
7-
fun convertToToken(username: String, clientId: String, requestedScopes: Set<String>, refreshToken: RefreshToken?): AccessToken
7+
fun convertToToken(username: String?, clientId: String, requestedScopes: Set<String>, refreshToken: RefreshToken?): AccessToken
88
}

oauth2-server-core/src/main/java/nl/myndocs/oauth2/token/converter/RefreshTokenConverter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ import nl.myndocs.oauth2.token.RefreshToken
44

55
interface RefreshTokenConverter {
66
fun convertToToken(refreshToken: RefreshToken): RefreshToken = convertToToken(refreshToken.username, refreshToken.clientId, refreshToken.scopes)
7-
fun convertToToken(username: String, clientId: String, requestedScopes: Set<String>): RefreshToken
7+
8+
fun convertToToken(username: String?, clientId: String, requestedScopes: Set<String>): RefreshToken
89
}

oauth2-server-core/src/main/java/nl/myndocs/oauth2/token/converter/UUIDAccessTokenConverter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class UUIDAccessTokenConverter(
99
private val accessTokenExpireInSeconds: Int = 3600
1010
) : AccessTokenConverter {
1111

12-
override fun convertToToken(username: String, clientId: String, requestedScopes: Set<String>, refreshToken: RefreshToken?): AccessToken {
12+
override fun convertToToken(username: String?, clientId: String, requestedScopes: Set<String>, refreshToken: RefreshToken?): AccessToken {
1313
return AccessToken(
1414
UUID.randomUUID().toString(),
1515
"bearer",

0 commit comments

Comments
 (0)