Skip to content

Commit 991bc87

Browse files
committed
Merge pull request #25 from dbalduini/play-2.2.x
Fix: Async DataHandler #23
2 parents f5ea4c7 + 4bfbdf3 commit 991bc87

File tree

14 files changed

+472
-269
lines changed

14 files changed

+472
-269
lines changed

play2-oauth2-provider/src/main/scala/scalaoauth2/provider/OAuth2Provider.scala

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package scalaoauth2.provider
22

33
import play.api.mvc._
44
import play.api.libs.json._
5+
import scala.concurrent.Await
6+
import scala.concurrent.Future
7+
import scala.concurrent.ExecutionContext.Implicits.global
58
import scala.language.implicitConversions
9+
import scala.concurrent.duration._
610

711
/**
812
* Basic OAuth2 provider trait.
@@ -110,12 +114,16 @@ trait OAuth2Provider extends OAuth2BaseProvider {
110114
* @return Request is successful then return JSON to client in OAuth 2.0 format.
111115
* Request is failed then return BadRequest or Unauthorized status to client with cause into the JSON.
112116
*/
113-
def issueAccessToken[A, U](dataHandler: DataHandler[U])(implicit request: play.api.mvc.Request[A]): SimpleResult = {
114-
TokenEndpoint.handleRequest(request, dataHandler) match {
115-
case Left(e) if e.statusCode == 400 => BadRequest(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
116-
case Left(e) if e.statusCode == 401 => Unauthorized(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
117-
case Right(r) => Ok(Json.toJson(responseAccessToken(r))).withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache")
117+
def issueAccessToken[A, U](dataHandler: DataHandler[U], timeout: Duration = 60.seconds)(implicit request: Request[A]): Result = {
118+
val f = TokenEndpoint.handleRequest(request, dataHandler).map { requestResult =>
119+
requestResult match {
120+
case Left(e) if e.statusCode == 400 => BadRequest(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
121+
case Left(e) if e.statusCode == 401 => Unauthorized(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
122+
case Right(r) => Ok(Json.toJson(responseAccessToken(r))).withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache")
123+
}
118124
}
125+
126+
Await.result(f, timeout)
119127
}
120128

121129
/**
@@ -128,12 +136,16 @@ trait OAuth2Provider extends OAuth2BaseProvider {
128136
* @return Authentication is successful then the response use your API result.
129137
* Authentication is failed then return BadRequest or Unauthorized status to client with cause into the JSON.
130138
*/
131-
def authorize[A, U](dataHandler: DataHandler[U])(callback: AuthInfo[U] => SimpleResult)(implicit request: play.api.mvc.Request[A]): SimpleResult = {
132-
ProtectedResource.handleRequest(request, dataHandler) match {
133-
case Left(e) if e.statusCode == 400 => BadRequest.withHeaders(responseOAuthErrorHeader(e))
134-
case Left(e) if e.statusCode == 401 => Unauthorized.withHeaders(responseOAuthErrorHeader(e))
135-
case Right(authInfo) => callback(authInfo)
139+
def authorize[A, U](dataHandler: DataHandler[U], timeout: Duration = 60.seconds)(callback: AuthInfo[U] => Result)(implicit request: Request[A]): Result = {
140+
val f = ProtectedResource.handleRequest(request, dataHandler).map { requestResult =>
141+
requestResult match {
142+
case Left(e) if e.statusCode == 400 => BadRequest.withHeaders(responseOAuthErrorHeader(e))
143+
case Left(e) if e.statusCode == 401 => Unauthorized.withHeaders(responseOAuthErrorHeader(e))
144+
case Right(authInfo) => callback(authInfo)
145+
}
136146
}
147+
148+
Await.result(f, timeout)
137149
}
138150

139151
}
@@ -170,8 +182,6 @@ trait OAuth2Provider extends OAuth2BaseProvider {
170182
*/
171183
trait OAuth2AsyncProvider extends OAuth2BaseProvider {
172184

173-
import scala.concurrent.Future
174-
175185
/**
176186
* Issue access token in DataHandler process and return the response to client.
177187
*
@@ -181,11 +191,13 @@ trait OAuth2AsyncProvider extends OAuth2BaseProvider {
181191
* @return Request is successful then return JSON to client in OAuth 2.0 format.
182192
* Request is failed then return BadRequest or Unauthorized status to client with cause into the JSON.
183193
*/
184-
def issueAccessToken[A, U](dataHandler: DataHandler[U])(implicit request: play.api.mvc.Request[A]): Future[SimpleResult] = {
185-
TokenEndpoint.handleRequest(request, dataHandler) match {
186-
case Left(e) if e.statusCode == 400 => Future.successful(BadRequest(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e)))
187-
case Left(e) if e.statusCode == 401 => Future.successful(Unauthorized(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e)))
188-
case Right(r) => Future.successful(Ok(Json.toJson(responseAccessToken(r))).withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache"))
194+
def issueAccessToken[A, U](dataHandler: DataHandler[U])(implicit request: Request[A]): Future[SimpleResult] = {
195+
TokenEndpoint.handleRequest(request, dataHandler).map { requestResult =>
196+
requestResult match {
197+
case Left(e) if e.statusCode == 400 => BadRequest(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
198+
case Left(e) if e.statusCode == 401 => Unauthorized(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
199+
case Right(r) => Ok(Json.toJson(responseAccessToken(r))).withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache")
200+
}
189201
}
190202
}
191203

@@ -199,12 +211,14 @@ trait OAuth2AsyncProvider extends OAuth2BaseProvider {
199211
* @return Authentication is successful then the response use your API result.
200212
* Authentication is failed then return BadRequest or Unauthorized status to client with cause into the JSON.
201213
*/
202-
def authorize[A, U](dataHandler: DataHandler[U])(callback: AuthInfo[U] => Future[SimpleResult])(implicit request: play.api.mvc.Request[A]): Future[SimpleResult] = {
203-
ProtectedResource.handleRequest(request, dataHandler) match {
204-
case Left(e) if e.statusCode == 400 => Future.successful(BadRequest.withHeaders(responseOAuthErrorHeader(e)))
205-
case Left(e) if e.statusCode == 401 => Future.successful(Unauthorized.withHeaders(responseOAuthErrorHeader(e)))
206-
case Right(authInfo) => callback(authInfo)
214+
def authorize[A, U](dataHandler: DataHandler[U])(callback: AuthInfo[U] => Future[SimpleResult])(implicit request: Request[A]): Future[SimpleResult] = {
215+
ProtectedResource.handleRequest(request, dataHandler).flatMap { requestResult =>
216+
requestResult match {
217+
case Left(e) if e.statusCode == 400 => Future.successful(BadRequest.withHeaders(responseOAuthErrorHeader(e)))
218+
case Left(e) if e.statusCode == 401 => Future.successful(Unauthorized.withHeaders(responseOAuthErrorHeader(e)))
219+
case Right(authInfo) => callback(authInfo)
220+
}
207221
}
208222
}
209223

210-
}
224+
}

project/Build.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Keys._
44
object ScalaOAuth2Build extends Build {
55

66
lazy val _organization = "com.nulab-inc"
7-
lazy val _version = "0.7.3"
7+
lazy val _version = "0.7.4"
88
lazy val _playVersion = "2.2.4"
99

1010
val _scalaVersion = "2.10.4"

scala-oauth2-core/src/main/scala/scalaoauth2/provider/DataHandler.scala

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scalaoauth2.provider
22

33
import java.util.Date
4+
import scala.concurrent.Future
45

56
case class AccessTokenRequest[U](clientId: String, clientSecret: String, user: U)
67

@@ -84,7 +85,7 @@ trait DataHandler[U] {
8485
* @param grantType Client send this value which is registered by application.
8586
* @return true if request is a regular client, false if request is a illegal client.
8687
*/
87-
def validateClient(clientId: String, clientSecret: String, grantType: String): Boolean
88+
def validateClient(clientId: String, clientSecret: String, grantType: String): Future[Boolean]
8889

8990
/**
9091
* Find userId with username and password these are used on your system.
@@ -94,15 +95,15 @@ trait DataHandler[U] {
9495
* @param password Client send this value which is used on your system.
9596
* @return Including UserId to Option if could find the user, None if couldn't find.
9697
*/
97-
def findUser(username: String, password: String): Option[U]
98+
def findUser(username: String, password: String): Future[Option[U]]
9899

99100
/**
100101
* Creates a new access token by authorized information.
101102
*
102103
* @param authInfo This value is already authorized by system.
103104
* @return Access token returns to client.
104105
*/
105-
def createAccessToken(authInfo: AuthInfo[U]): AccessToken
106+
def createAccessToken(authInfo: AuthInfo[U]): Future[AccessToken]
106107

107108
/**
108109
* Returns stored access token by authorized information.
@@ -112,15 +113,15 @@ trait DataHandler[U] {
112113
* @param authInfo This value is already authorized by system.
113114
* @return Access token returns to client.
114115
*/
115-
def getStoredAccessToken(authInfo: AuthInfo[U]): Option[AccessToken]
116+
def getStoredAccessToken(authInfo: AuthInfo[U]): Future[Option[AccessToken]]
116117

117118
/**
118119
* Creates a new access token by refreshToken.
119120
*
120121
* @param authInfo This value is already authorized by system.
121122
* @return Access token returns to client.
122123
*/
123-
def refreshAccessToken(authInfo: AuthInfo[U], refreshToken: String): AccessToken
124+
def refreshAccessToken(authInfo: AuthInfo[U], refreshToken: String): Future[AccessToken]
124125

125126
/**
126127
* Find authorized information by authorization code.
@@ -130,7 +131,7 @@ trait DataHandler[U] {
130131
* @param code Client send authorization code which is registered by system.
131132
* @return Return authorized information that matched the code.
132133
*/
133-
def findAuthInfoByCode(code: String): Option[AuthInfo[U]]
134+
def findAuthInfoByCode(code: String): Future[Option[AuthInfo[U]]]
134135

135136
/**
136137
* Find authorized information by refresh token.
@@ -140,34 +141,34 @@ trait DataHandler[U] {
140141
* @param refreshToken Client send refresh token which is created by system.
141142
* @return Return authorized information that matched the refresh token.
142143
*/
143-
def findAuthInfoByRefreshToken(refreshToken: String): Option[AuthInfo[U]]
144+
def findAuthInfoByRefreshToken(refreshToken: String): Future[Option[AuthInfo[U]]]
144145

145146
/**
146-
* Find userId by clientId and clientSecret.
147+
* Find user by clientId and clientSecret.
147148
*
148149
* If you don't support Client Credentials Grant then doesn't need implementing.
149150
*
150151
* @param clientId Client send this value which is registered by application.
151152
* @param clientSecret Client send this value which is registered by application.
152153
* @return Return user that matched both values.
153154
*/
154-
def findClientUser(clientId: String, clientSecret: String, scope: Option[String]): Option[U]
155+
def findClientUser(clientId: String, clientSecret: String, scope: Option[String]): Future[Option[U]]
155156

156157
/**
157158
* Find AccessToken object by access token code.
158159
*
159160
* @param token Client send access token which is created by system.
160161
* @return Return access token that matched the token.
161162
*/
162-
def findAccessToken(token: String): Option[AccessToken]
163+
def findAccessToken(token: String): Future[Option[AccessToken]]
163164

164165
/**
165166
* Find authorized information by access token.
166167
*
167168
* @param accessToken This value is AccessToken.
168169
* @return Return authorized information if the parameter is available.
169170
*/
170-
def findAuthInfoByAccessToken(accessToken: AccessToken): Option[AuthInfo[U]]
171+
def findAuthInfoByAccessToken(accessToken: AccessToken): Future[Option[AuthInfo[U]]]
171172

172173
/**
173174
* Check expiration.
Lines changed: 68 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,124 @@
11
package scalaoauth2.provider
22

3+
import scala.concurrent.Future
4+
import scala.concurrent.ExecutionContext.Implicits.global
35

46
case class GrantHandlerResult(tokenType: String, accessToken: String, expiresIn: Option[Long], refreshToken: Option[String], scope: Option[String])
57

68
trait GrantHandler {
79

8-
def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): GrantHandlerResult
10+
def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): Future[GrantHandlerResult]
911

1012

1113
/**
1214
* Returns valid access token.
13-
*
15+
*
1416
* @param dataHandler
1517
* @param authInfo
16-
* @return
18+
* @return
1719
*/
18-
def issueAccessToken[U](dataHandler: DataHandler[U], authInfo: AuthInfo[U]): GrantHandlerResult = {
19-
val accessToken = dataHandler.getStoredAccessToken(authInfo) match {
20-
case Some(token) if dataHandler.isAccessTokenExpired(token) =>
21-
token.refreshToken.map(dataHandler.refreshAccessToken(authInfo, _)).getOrElse(dataHandler.createAccessToken(authInfo))
22-
case Some(token) => token
23-
case None => dataHandler.createAccessToken(authInfo)
20+
def issueAccessToken[U](dataHandler: DataHandler[U], authInfo: AuthInfo[U]): Future[GrantHandlerResult] = {
21+
dataHandler.getStoredAccessToken(authInfo).flatMap { optionalAccessToken =>
22+
(optionalAccessToken match {
23+
case Some(token) if dataHandler.isAccessTokenExpired(token) => {
24+
token.refreshToken.map(dataHandler.refreshAccessToken(authInfo, _)).getOrElse(dataHandler.createAccessToken(authInfo))
25+
}
26+
case Some(token) => Future.successful(token)
27+
case None => dataHandler.createAccessToken(authInfo)
28+
}).map { accessToken =>
29+
GrantHandlerResult(
30+
"Bearer",
31+
accessToken.token,
32+
accessToken.expiresIn,
33+
accessToken.refreshToken,
34+
accessToken.scope
35+
)
36+
}
2437
}
25-
26-
GrantHandlerResult(
27-
"Bearer",
28-
accessToken.token,
29-
accessToken.expiresIn,
30-
accessToken.refreshToken,
31-
accessToken.scope
32-
)
3338
}
3439
}
3540

3641
class RefreshToken(clientCredentialFetcher: ClientCredentialFetcher) extends GrantHandler {
3742

38-
override def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): GrantHandlerResult = {
43+
override def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): Future[GrantHandlerResult] = {
3944
val clientCredential = clientCredentialFetcher.fetch(request).getOrElse(throw new InvalidRequest("BadRequest"))
4045
val refreshToken = request.requireRefreshToken
41-
val authInfo = dataHandler.findAuthInfoByRefreshToken(refreshToken).getOrElse(throw new InvalidGrant("NotFound"))
42-
if (authInfo.clientId != clientCredential.clientId) {
43-
throw new InvalidClient
46+
47+
dataHandler.findAuthInfoByRefreshToken(refreshToken).flatMap { authInfoOption =>
48+
val authInfo = authInfoOption.getOrElse(throw new InvalidGrant("NotFound"))
49+
if (authInfo.clientId != clientCredential.clientId) {
50+
throw new InvalidClient
51+
}
52+
53+
dataHandler.refreshAccessToken(authInfo, refreshToken).map { accessToken =>
54+
GrantHandlerResult(
55+
"Bearer",
56+
accessToken.token,
57+
accessToken.expiresIn,
58+
accessToken.refreshToken,
59+
accessToken.scope
60+
)
61+
}
4462
}
45-
46-
val accessToken = dataHandler.refreshAccessToken(authInfo, refreshToken)
47-
GrantHandlerResult(
48-
"Bearer",
49-
accessToken.token,
50-
accessToken.expiresIn,
51-
accessToken.refreshToken,
52-
accessToken.scope
53-
)
5463
}
5564
}
5665

5766
class Password(clientCredentialFetcher: ClientCredentialFetcher) extends GrantHandler {
5867

59-
override def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): GrantHandlerResult = {
68+
override def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): Future[GrantHandlerResult] = {
6069
val clientCredential = clientCredentialFetcher.fetch(request).getOrElse(throw new InvalidRequest("BadRequest"))
6170
val username = request.requireUsername
6271
val password = request.requirePassword
63-
val user = dataHandler.findUser(username, password).getOrElse(throw new InvalidGrant())
64-
val scope = request.scope
65-
val clientId = clientCredential.clientId
66-
val authInfo = AuthInfo(user, clientId, scope, None)
6772

68-
issueAccessToken(dataHandler, authInfo)
73+
dataHandler.findUser(username, password).flatMap { userOption =>
74+
val user = userOption.getOrElse(throw new InvalidGrant("username or password is incorrect"))
75+
val scope = request.scope
76+
val clientId = clientCredential.clientId
77+
val authInfo = AuthInfo(user, clientId, scope, None)
78+
79+
issueAccessToken(dataHandler, authInfo)
80+
}
6981
}
7082
}
7183

7284
class ClientCredentials(clientCredentialFetcher: ClientCredentialFetcher) extends GrantHandler {
7385

74-
override def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): GrantHandlerResult = {
86+
override def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): Future[GrantHandlerResult] = {
7587
val clientCredential = clientCredentialFetcher.fetch(request).getOrElse(throw new InvalidRequest("BadRequest"))
7688
val clientSecret = clientCredential.clientSecret
7789
val clientId = clientCredential.clientId
7890
val scope = request.scope
79-
val user = dataHandler.findClientUser(clientId, clientSecret, scope).getOrElse(throw new InvalidGrant())
80-
val authInfo = AuthInfo(user, clientId, scope, None)
81-
82-
issueAccessToken(dataHandler, authInfo)
91+
92+
dataHandler.findClientUser(clientId, clientSecret, scope).flatMap { userOption =>
93+
val user = userOption.getOrElse(throw new InvalidGrant())
94+
val authInfo = AuthInfo(user, clientId, scope, None)
95+
96+
issueAccessToken(dataHandler, authInfo)
97+
}
8398
}
8499

85100
}
86101

87102
class AuthorizationCode(clientCredentialFetcher: ClientCredentialFetcher) extends GrantHandler {
88103

89-
override def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): GrantHandlerResult = {
104+
override def handleRequest[U](request: AuthorizationRequest, dataHandler: DataHandler[U]): Future[GrantHandlerResult] = {
90105
val clientCredential = clientCredentialFetcher.fetch(request).getOrElse(throw new InvalidRequest("BadRequest"))
91106
val clientId = clientCredential.clientId
92107
val code = request.requireCode
93108
val redirectUri = request.redirectUri
94-
val authInfo = dataHandler.findAuthInfoByCode(code).getOrElse(throw new InvalidGrant())
95-
if (authInfo.clientId != clientId) {
96-
throw new InvalidClient
97-
}
98109

99-
if (authInfo.redirectUri.isDefined && authInfo.redirectUri != redirectUri) {
100-
throw new RedirectUriMismatch
101-
}
110+
dataHandler.findAuthInfoByCode(code).flatMap { authInfoOption =>
111+
val authInfo = authInfoOption.getOrElse(throw new InvalidGrant())
112+
if (authInfo.clientId != clientId) {
113+
throw new InvalidClient
114+
}
115+
116+
if (authInfo.redirectUri.isDefined && authInfo.redirectUri != redirectUri) {
117+
throw new RedirectUriMismatch
118+
}
102119

103-
issueAccessToken(dataHandler, authInfo)
120+
issueAccessToken(dataHandler, authInfo)
121+
}
104122
}
105123

106124
}

0 commit comments

Comments
 (0)