Skip to content

Commit 95d60be

Browse files
authored
Configuration (#12)
* Align configuration over modules Make TokenService configurable * Configuration should not be aware of which TokenService is being implemented
1 parent 0fda05d commit 95d60be

File tree

12 files changed

+476
-401
lines changed

12 files changed

+476
-401
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import nl.myndocs.oauth2.token.toMap
88

99
class CallRouter(
1010
private val tokenService: TokenService,
11-
private val tokenEndpoint: String,
12-
private val authorizeEndpoint: String,
13-
private val userinfoEndpoint: String,
11+
val tokenEndpoint: String,
12+
val authorizeEndpoint: String,
13+
val userInfoEndpoint: String,
1414
private val userInfoCallback: (UserInfo) -> Map<String, Any?>
1515
) {
1616
companion object {
@@ -28,7 +28,7 @@ class CallRouter(
2828
when (callContext.path) {
2929
tokenEndpoint -> routeTokenEndpoint(callContext)
3030
authorizeEndpoint -> routeAuthorizeEndpoint(callContext, authorizer)
31-
userinfoEndpoint -> routeUserInfoEndpoint(callContext)
31+
userInfoEndpoint -> routeUserInfoEndpoint(callContext)
3232
}
3333
}
3434

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
package nl.myndocs.oauth2
2+
3+
import nl.myndocs.oauth2.authenticator.Authenticator
4+
import nl.myndocs.oauth2.authenticator.IdentityScopeVerifier
5+
import nl.myndocs.oauth2.client.ClientService
6+
import nl.myndocs.oauth2.exception.*
7+
import nl.myndocs.oauth2.identity.IdentityService
8+
import nl.myndocs.oauth2.identity.UserInfo
9+
import nl.myndocs.oauth2.request.*
10+
import nl.myndocs.oauth2.response.TokenResponse
11+
import nl.myndocs.oauth2.scope.ScopeParser
12+
import nl.myndocs.oauth2.token.AccessToken
13+
import nl.myndocs.oauth2.token.CodeToken
14+
import nl.myndocs.oauth2.token.TokenStore
15+
import nl.myndocs.oauth2.token.converter.AccessTokenConverter
16+
import nl.myndocs.oauth2.token.converter.CodeTokenConverter
17+
import nl.myndocs.oauth2.token.converter.RefreshTokenConverter
18+
19+
class Oauth2TokenService(
20+
private val identityService: IdentityService,
21+
private val clientService: ClientService,
22+
private val tokenStore: TokenStore,
23+
private val accessTokenConverter: AccessTokenConverter,
24+
private val refreshTokenConverter: RefreshTokenConverter,
25+
private val codeTokenConverter: CodeTokenConverter
26+
) : TokenService {
27+
private val INVALID_REQUEST_FIELD_MESSAGE = "'%s' field is missing"
28+
/**
29+
* @throws InvalidIdentityException
30+
* @throws InvalidClientException
31+
* @throws InvalidScopeException
32+
*/
33+
override fun authorize(passwordGrantRequest: PasswordGrantRequest): TokenResponse {
34+
throwExceptionIfUnverifiedClient(passwordGrantRequest)
35+
36+
if (passwordGrantRequest.username == null) {
37+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
38+
}
39+
40+
if (passwordGrantRequest.password == null) {
41+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
42+
}
43+
44+
val requestedClient = clientService.clientOf(
45+
passwordGrantRequest.clientId!!
46+
)!!
47+
val requestedIdentity = identityService.identityOf(
48+
requestedClient, passwordGrantRequest.username
49+
)
50+
51+
if (requestedIdentity == null || !identityService.validCredentials(requestedClient, requestedIdentity, passwordGrantRequest.password)) {
52+
throw InvalidIdentityException()
53+
}
54+
55+
var requestedScopes = ScopeParser.parseScopes(passwordGrantRequest.scope)
56+
.toSet()
57+
58+
if (passwordGrantRequest.scope == null) {
59+
requestedScopes = requestedClient.clientScopes
60+
}
61+
62+
val scopesAllowed = scopesAllowed(requestedClient.clientScopes, requestedScopes)
63+
64+
if (!scopesAllowed) {
65+
throw InvalidScopeException(requestedScopes.minus(requestedClient.clientScopes))
66+
}
67+
68+
if (!identityService.validScopes(requestedClient, requestedIdentity, requestedScopes)) {
69+
throw InvalidScopeException(requestedScopes)
70+
}
71+
72+
val accessToken = accessTokenConverter.convertToToken(
73+
requestedIdentity.username,
74+
requestedClient.clientId,
75+
requestedScopes,
76+
refreshTokenConverter.convertToToken(
77+
requestedIdentity.username,
78+
requestedClient.clientId,
79+
requestedScopes
80+
)
81+
)
82+
83+
tokenStore.storeAccessToken(accessToken)
84+
85+
return accessToken.toTokenResponse()
86+
}
87+
88+
override fun authorize(authorizationCodeRequest: AuthorizationCodeRequest): TokenResponse {
89+
throwExceptionIfUnverifiedClient(authorizationCodeRequest)
90+
91+
if (authorizationCodeRequest.code == null) {
92+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("code"))
93+
}
94+
95+
if (authorizationCodeRequest.redirectUri == null) {
96+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
97+
}
98+
99+
val consumeCodeToken = tokenStore.consumeCodeToken(authorizationCodeRequest.code)
100+
?: throw InvalidGrantException()
101+
102+
103+
if (consumeCodeToken.redirectUri != authorizationCodeRequest.redirectUri || consumeCodeToken.clientId != authorizationCodeRequest.clientId) {
104+
throw InvalidGrantException()
105+
}
106+
107+
val accessToken = accessTokenConverter.convertToToken(
108+
consumeCodeToken.username,
109+
consumeCodeToken.clientId,
110+
consumeCodeToken.scopes,
111+
refreshTokenConverter.convertToToken(
112+
consumeCodeToken.username,
113+
consumeCodeToken.clientId,
114+
consumeCodeToken.scopes
115+
)
116+
)
117+
118+
tokenStore.storeAccessToken(accessToken)
119+
120+
return accessToken.toTokenResponse()
121+
}
122+
123+
override fun refresh(refreshTokenRequest: RefreshTokenRequest): TokenResponse {
124+
throwExceptionIfUnverifiedClient(refreshTokenRequest)
125+
126+
if (refreshTokenRequest.refreshToken == null) {
127+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("refresh_token"))
128+
}
129+
130+
val refreshToken = tokenStore.refreshToken(refreshTokenRequest.refreshToken) ?: throw InvalidGrantException()
131+
132+
if (refreshToken.clientId != refreshTokenRequest.clientId) {
133+
throw InvalidGrantException()
134+
}
135+
136+
val accessToken = accessTokenConverter.convertToToken(
137+
refreshToken.username,
138+
refreshToken.clientId,
139+
refreshToken.scopes,
140+
refreshToken
141+
)
142+
143+
tokenStore.storeAccessToken(accessToken)
144+
145+
return accessToken.toTokenResponse()
146+
}
147+
148+
override fun redirect(
149+
redirect: RedirectAuthorizationCodeRequest,
150+
authenticator: Authenticator?,
151+
identityScopeVerifier: IdentityScopeVerifier?
152+
): CodeToken {
153+
if (redirect.clientId == null) {
154+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
155+
}
156+
157+
if (redirect.username == null) {
158+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
159+
}
160+
161+
if (redirect.password == null) {
162+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
163+
}
164+
if (redirect.redirectUri == null) {
165+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
166+
}
167+
168+
val clientOf = clientService.clientOf(redirect.clientId) ?: throw InvalidClientException()
169+
170+
if (!clientOf.redirectUris.contains(redirect.redirectUri)) {
171+
throw InvalidGrantException("invalid 'redirect_uri'")
172+
}
173+
174+
val identityOf = identityService.identityOf(clientOf, redirect.username) ?: throw InvalidIdentityException()
175+
176+
var validIdentity = authenticator?.validCredentials(clientOf, identityOf, redirect.password)
177+
?: identityService.validCredentials(clientOf, identityOf, redirect.password)
178+
179+
if (!validIdentity) {
180+
throw InvalidIdentityException()
181+
}
182+
183+
var requestedScopes = ScopeParser.parseScopes(redirect.scope)
184+
185+
if (redirect.scope == null) {
186+
requestedScopes = clientOf.clientScopes
187+
}
188+
189+
val scopesAllowed = identityScopeVerifier?.validScopes(clientOf, identityOf, requestedScopes)
190+
?: scopesAllowed(clientOf.clientScopes, requestedScopes)
191+
192+
if (!scopesAllowed) {
193+
throw InvalidScopeException(requestedScopes.minus(clientOf.clientScopes))
194+
}
195+
196+
if (!identityService.validScopes(clientOf, identityOf, requestedScopes)) {
197+
throw InvalidScopeException(requestedScopes)
198+
}
199+
200+
val codeToken = codeTokenConverter.convertToToken(
201+
identityOf.username,
202+
clientOf.clientId,
203+
redirect.redirectUri,
204+
requestedScopes
205+
)
206+
207+
tokenStore.storeCodeToken(codeToken)
208+
209+
return codeToken
210+
}
211+
212+
override fun redirect(
213+
redirect: RedirectTokenRequest,
214+
authenticator: Authenticator?,
215+
identityScopeVerifier: IdentityScopeVerifier?
216+
): AccessToken {
217+
if (redirect.clientId == null) {
218+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
219+
}
220+
221+
if (redirect.username == null) {
222+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("username"))
223+
}
224+
225+
if (redirect.password == null) {
226+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("password"))
227+
}
228+
if (redirect.redirectUri == null) {
229+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("redirect_uri"))
230+
}
231+
232+
val clientOf = clientService.clientOf(redirect.clientId) ?: throw InvalidClientException()
233+
234+
if (!clientOf.redirectUris.contains(redirect.redirectUri)) {
235+
throw InvalidGrantException("invalid 'redirect_uri'")
236+
}
237+
238+
val identityOf = identityService.identityOf(clientOf, redirect.username) ?: throw InvalidIdentityException()
239+
240+
var validIdentity = authenticator?.validCredentials(clientOf, identityOf, redirect.password)
241+
?: identityService.validCredentials(clientOf, identityOf, redirect.password)
242+
243+
if (!validIdentity) {
244+
throw InvalidIdentityException()
245+
}
246+
247+
var requestedScopes = ScopeParser.parseScopes(redirect.scope)
248+
249+
if (redirect.scope == null) {
250+
requestedScopes = clientOf.clientScopes
251+
}
252+
253+
val scopesAllowed = identityScopeVerifier?.validScopes(clientOf, identityOf, requestedScopes)
254+
?: scopesAllowed(clientOf.clientScopes, requestedScopes)
255+
if (!scopesAllowed) {
256+
throw InvalidScopeException(requestedScopes.minus(clientOf.clientScopes))
257+
}
258+
259+
if (!identityService.validScopes(clientOf, identityOf, requestedScopes)) {
260+
throw InvalidScopeException(requestedScopes)
261+
}
262+
263+
val accessToken = accessTokenConverter.convertToToken(
264+
identityOf.username,
265+
clientOf.clientId,
266+
requestedScopes,
267+
null
268+
)
269+
270+
tokenStore.storeAccessToken(accessToken)
271+
272+
return accessToken
273+
}
274+
275+
override fun userInfo(accessToken: String): UserInfo {
276+
val storedAccessToken = tokenStore.accessToken(accessToken)!!
277+
val client = clientService.clientOf(storedAccessToken.clientId)!!
278+
val identity = identityService.identityOf(client, storedAccessToken.username)!!
279+
280+
return UserInfo(
281+
identity,
282+
client,
283+
storedAccessToken.scopes
284+
)
285+
}
286+
287+
private fun throwExceptionIfUnverifiedClient(clientRequest: ClientRequest) {
288+
if (clientRequest.clientId == null) {
289+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_id"))
290+
}
291+
292+
if (clientRequest.clientSecret == null) {
293+
throw InvalidRequestException(INVALID_REQUEST_FIELD_MESSAGE.format("client_secret"))
294+
}
295+
296+
val client = clientService.clientOf(clientRequest.clientId!!) ?: throw InvalidClientException()
297+
298+
if (!clientService.validClient(client, clientRequest.clientSecret!!)) {
299+
throw InvalidClientException()
300+
}
301+
}
302+
303+
private fun scopesAllowed(clientScopes: Set<String>, requestedScopes: Set<String>): Boolean {
304+
return clientScopes.containsAll(requestedScopes)
305+
}
306+
307+
private fun AccessToken.toTokenResponse() = TokenResponse(
308+
accessToken,
309+
tokenType,
310+
expiresIn(),
311+
refreshToken?.refreshToken
312+
)
313+
}

0 commit comments

Comments
 (0)