Skip to content

Commit 07227f5

Browse files
committed
Make TokenService responsible for not set properties to make framework adoption easier
Add InvalidRequestException for missing fields IdentityService should also be able to validate scopes for more flexibility BasicAuth should return nullables
1 parent 8b84bdc commit 07227f5

File tree

23 files changed

+438
-165
lines changed

23 files changed

+438
-165
lines changed

kotlin-oauth2-server-core/pom.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,25 @@
1010
<modelVersion>4.0.0</modelVersion>
1111

1212
<artifactId>kotlin-oauth2-server-core</artifactId>
13+
14+
<dependencies>
15+
<dependency>
16+
<groupId>org.junit.jupiter</groupId>
17+
<artifactId>junit-jupiter-engine</artifactId>
18+
<version>5.2.0</version>
19+
<scope>test</scope>
20+
</dependency>
21+
<dependency>
22+
<groupId>io.mockk</groupId>
23+
<artifactId>mockk</artifactId>
24+
<version>1.8.6</version>
25+
<scope>test</scope>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.hamcrest</groupId>
29+
<artifactId>hamcrest-library</artifactId>
30+
<version>1.3</version>
31+
<scope>test</scope>
32+
</dependency>
33+
</dependencies>
1334
</project>

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

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

33
import nl.myndocs.oauth2.client.ClientService
4-
import nl.myndocs.oauth2.exception.InvalidClientException
5-
import nl.myndocs.oauth2.exception.InvalidGrantException
6-
import nl.myndocs.oauth2.exception.InvalidIdentityException
7-
import nl.myndocs.oauth2.exception.InvalidScopeException
4+
import nl.myndocs.oauth2.exception.*
85
import nl.myndocs.oauth2.identity.IdentityService
96
import nl.myndocs.oauth2.request.*
107
import nl.myndocs.oauth2.response.TokenResponse
@@ -24,6 +21,7 @@ class TokenService(
2421
private val refreshTokenConverter: RefreshTokenConverter,
2522
private val codeTokenConverter: CodeTokenConverter
2623
) {
24+
private val INVALID_REQUEST_FIELD_MESSAGE = "'%s' field is missing"
2725
/**
2826
* @throws InvalidIdentityException
2927
* @throws InvalidClientException
@@ -32,28 +30,40 @@ class TokenService(
3230
fun authorize(passwordGrantRequest: PasswordGrantRequest): TokenResponse {
3331
throwExceptionIfUnverifiedClient(passwordGrantRequest)
3432

33+
if (passwordGrantRequest.username == null) {
34+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
35+
}
36+
37+
if (passwordGrantRequest.password == null) {
38+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
39+
}
40+
3541
val requestedClient = clientService.clientOf(
36-
passwordGrantRequest.clientId
42+
passwordGrantRequest.clientId!!
3743
)!!
3844
val requestedIdentity = identityService.identityOf(
3945
requestedClient, passwordGrantRequest.username
4046
)
4147

42-
if (requestedIdentity == null || !identityService.validIdentity(requestedClient, requestedIdentity, passwordGrantRequest.password)) {
48+
if (requestedIdentity == null || !identityService.validCredentials(requestedClient, requestedIdentity, passwordGrantRequest.password)) {
4349
throw InvalidIdentityException()
4450
}
4551

4652
var requestedScopes = ScopeParser.parseScopes(passwordGrantRequest.scope)
4753
.toSet()
4854

49-
if (requestedScopes.isEmpty()) {
55+
if (passwordGrantRequest.scope == null) {
5056
requestedScopes = requestedClient.clientScopes
5157
}
5258

53-
val clientDiffScopes = diffScopes(requestedClient.clientScopes, requestedScopes)
59+
val scopesAllowed = scopesAllowed(requestedClient.clientScopes, requestedScopes)
5460

55-
if (clientDiffScopes.isNotEmpty()) {
56-
throw InvalidScopeException(clientDiffScopes)
61+
if (!scopesAllowed) {
62+
throw InvalidScopeException(requestedScopes.minus(requestedClient.clientScopes))
63+
}
64+
65+
if (!identityService.validScopes(requestedClient, requestedIdentity, requestedScopes)) {
66+
throw InvalidScopeException(requestedScopes)
5767
}
5868

5969
val accessToken = accessTokenConverter.convertToToken(
@@ -75,6 +85,14 @@ class TokenService(
7585
fun authorize(authorizationCodeRequest: AuthorizationCodeRequest): TokenResponse {
7686
throwExceptionIfUnverifiedClient(authorizationCodeRequest)
7787

88+
if (authorizationCodeRequest.code == null) {
89+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("code"))
90+
}
91+
92+
if (authorizationCodeRequest.redirectUri == null) {
93+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
94+
}
95+
7896
val consumeCodeToken = tokenStore.consumeCodeToken(authorizationCodeRequest.code)
7997
?: throw InvalidGrantException()
8098

@@ -102,6 +120,10 @@ class TokenService(
102120
fun refresh(refreshTokenRequest: RefreshTokenRequest): TokenResponse {
103121
throwExceptionIfUnverifiedClient(refreshTokenRequest)
104122

123+
if (refreshTokenRequest.refreshToken == null) {
124+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("refresh_token"))
125+
}
126+
105127
val refreshToken = tokenStore.refreshToken(refreshTokenRequest.refreshToken) ?: throw InvalidGrantException()
106128

107129
val accessToken = accessTokenConverter.convertToToken(
@@ -117,20 +139,40 @@ class TokenService(
117139
}
118140

119141
fun redirect(redirect: RedirectAuthorizationCodeRequest): CodeToken {
142+
if (redirect.clientId == null) {
143+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
144+
}
145+
146+
if (redirect.username == null) {
147+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
148+
}
149+
150+
if (redirect.password == null) {
151+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
152+
}
153+
if (redirect.redirectUri == null) {
154+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
155+
}
156+
120157
val clientOf = clientService.clientOf(redirect.clientId) ?: throw InvalidClientException()
158+
159+
if (!clientOf.redirectUris.contains(redirect.redirectUri)) {
160+
throw InvalidGrantException("invalid 'redirect_uri'")
161+
}
162+
121163
val identityOf = identityService.identityOf(clientOf, redirect.username) ?: throw InvalidIdentityException()
122164

123-
var validIdentity = identityService.validIdentity(clientOf, identityOf, redirect.password)
165+
var validIdentity = identityService.validCredentials(clientOf, identityOf, redirect.password)
124166

125167
if (!validIdentity) {
126168
throw InvalidIdentityException()
127169
}
128170

129171
val requestedScopes = ScopeParser.parseScopes(redirect.scope)
130172

131-
val diffScopes = diffScopes(clientOf.clientScopes, requestedScopes)
132-
if (diffScopes.isNotEmpty()) {
133-
throw InvalidScopeException(diffScopes)
173+
val scopesAllowed = scopesAllowed(clientOf.clientScopes, requestedScopes)
174+
if (!scopesAllowed) {
175+
throw InvalidScopeException(requestedScopes.minus(clientOf.clientScopes))
134176
}
135177

136178
val codeToken = codeTokenConverter.convertToToken(
@@ -147,19 +189,23 @@ class TokenService(
147189
}
148190

149191
private fun throwExceptionIfUnverifiedClient(clientRequest: ClientRequest) {
150-
val client = clientService.clientOf(clientRequest.clientId) ?: throw InvalidClientException()
192+
if (clientRequest.clientId == null) {
193+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
194+
}
151195

152-
if (!clientService.validClient(client, clientRequest.clientSecret)) {
153-
throw InvalidClientException()
196+
if (clientRequest.clientSecret == null) {
197+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_secret"))
154198
}
155-
}
156199

157-
private fun diffScopes(allowedScopes: Set<String>, validationScopes: Set<String>): Set<String> {
158-
if (allowedScopes.containsAll(validationScopes)) {
159-
return validationScopes.minus(allowedScopes)
200+
val client = clientService.clientOf(clientRequest.clientId!!) ?: throw InvalidClientException()
201+
202+
if (!clientService.validClient(client, clientRequest.clientSecret!!)) {
203+
throw InvalidClientException()
160204
}
205+
}
161206

162-
return setOf()
207+
private fun scopesAllowed(clientScopes: Set<String>, requestedScopes: Set<String>): Boolean {
208+
return clientScopes.containsAll(requestedScopes)
163209
}
164210

165211
private fun AccessToken.toTokenResponse() = TokenResponse(
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package nl.myndocs.oauth2.exception
22

3-
class InvalidGrantException : OauthException(OauthError.INVALID_GRANT)
3+
open class InvalidGrantException(message: String? = null) : OauthException(OauthError.INVALID_GRANT, message)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package nl.myndocs.oauth2.exception
22

3-
class InvalidIdentityException : Exception()
3+
class InvalidIdentityException : InvalidGrantException()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package nl.myndocs.oauth2.exception
2+
3+
class InvalidRequestException(message: String) : OauthException(OauthError.INVALID_REQUEST, message)

kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/identity/IdentityService.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ import nl.myndocs.oauth2.client.Client
55
interface IdentityService {
66
fun identityOf(forClient: Client, username: String): Identity?
77

8-
fun validIdentity(forClient: Client, identity: Identity, password: String): Boolean
8+
fun validCredentials(forClient: Client, identity: Identity, password: String): Boolean
9+
10+
fun validScopes(forClient: Client, identity: Identity, scopes: Set<String>): Boolean
911
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package nl.myndocs.oauth2.request
22

33
data class AuthorizationCodeRequest(
4-
override val clientId: String,
5-
override val clientSecret: String,
6-
val code: String,
7-
val redirectUri: String
4+
override val clientId: String?,
5+
override val clientSecret: String?,
6+
val code: String?,
7+
val redirectUri: String?
88
) : ClientRequest {
99
val grant_type = "authorization_code"
1010
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package nl.myndocs.oauth2.request
22

33
interface ClientRequest {
4-
val clientId: String
5-
val clientSecret: String
4+
val clientId: String?
5+
val clientSecret: String?
66
}

kotlin-oauth2-server-core/src/main/java/nl/myndocs/oauth2/request/PasswordGrantRequest.kt

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

33
data class PasswordGrantRequest(
4-
override val clientId: String,
5-
override val clientSecret: String,
6-
val username: String,
7-
val password: String,
4+
override val clientId: String?,
5+
override val clientSecret: String?,
6+
val username: String?,
7+
val password: String?,
88
val scope: String?
99
) : ClientRequest {
1010
val grant_type = "password"
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package nl.myndocs.oauth2.request
22

33
class RedirectAuthorizationCodeRequest(
4-
val clientId: String,
5-
val redirectUri: String,
6-
val username: String,
7-
val password: String,
4+
val clientId: String?,
5+
val redirectUri: String?,
6+
val username: String?,
7+
val password: String?,
88
val scope: String?
99
)

0 commit comments

Comments
 (0)