Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a49cfcd
define trait type explicitly
tsuyoshizawa Aug 25, 2014
494b180
version 0.9.0
tsuyoshizawa Aug 30, 2014
28cdf2b
Add notification
Sep 2, 2014
1a98f76
version 0.9.1-SNAPSHOT
tsuyoshizawa Sep 17, 2014
c43a668
Make AuthInfo[+U] covariant
dbalduini Sep 18, 2014
e550c27
Merge pull request #29 from dbalduini/auth-covariant
tsuyoshizawa Sep 18, 2014
46e5ff1
version 0.9.1
tsuyoshizawa Sep 22, 2014
29dc8c8
Make client credentials optional for password grant.
Oct 4, 2014
9f5db6e
Merge pull request #34 from ctoomey/client-cred-optional-for-password
tsuyoshizawa Oct 8, 2014
f0b639c
version 0.10.0-SNAPSHOT
tsuyoshizawa Oct 8, 2014
831c0da
add supporting version to README.md #33
tsuyoshizawa Oct 9, 2014
c04307d
Refactoring
tsuyoshizawa Oct 3, 2014
8cc4fe8
remove duplicate clientcredential parsing #35
tsuyoshizawa Oct 14, 2014
0ea65aa
modify optional clientSecret #36
tsuyoshizawa Oct 15, 2014
f501865
update README.md #36
tsuyoshizawa Oct 15, 2014
1a54cd4
rename Option instance
tsuyoshizawa Oct 16, 2014
487008e
add about AuthInfo #36
tsuyoshizawa Oct 16, 2014
aefa576
Merge branch 'optional-clientsecret'
tsuyoshizawa Oct 18, 2014
69a508d
version 0.10.0
tsuyoshizawa Oct 18, 2014
e9204fa
modify README.md for 0.10.0
tsuyoshizawa Oct 18, 2014
8ba2057
Fix some warnings
dbalduini Oct 20, 2014
579b7ed
Add ResourceHandler
dbalduini Oct 20, 2014
0d1137d
Add ProtectedResourceHandler. Add AuthorizationHandler.
dbalduini Oct 21, 2014
da2fd52
Merge pull request #38 from dbalduini/break-client-auth
tsuyoshizawa Oct 21, 2014
bf75f4b
version 0.11.0
tsuyoshizawa Oct 27, 2014
39e4c37
Merged in 0.11.0 master
Nov 3, 2014
ea22378
Now compiling merged with 0.11
Nov 3, 2014
701e55e
Added expiresIn method to AuthCode case class
Nov 7, 2014
c0f779f
GrantHandler for token request was comparing old non-Option clientId …
Nov 7, 2014
52c045d
bumped version to 0.11.1
Nov 12, 2014
9d2ae9c
Switched to publishing to S3
Nov 12, 2014
1c2d945
Removed addSbtPlugin line from Build.scala
Nov 12, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ language: scala
scala:
- 2.10.4
- 2.11.2
notifications:
webhooks:
urls:
- https://nulab-inc.com/hubot/travis?room=3741
on_success: change
49 changes: 44 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ If you'd like to use this with Playframework, add "play2-oauth2-provider" to lib

```scala
libraryDependencies ++= Seq(
"com.nulab-inc" %% "play2-oauth2-provider" % "0.8.0"
"com.nulab-inc" %% "play2-oauth2-provider" % "0.11.0"
)
```

### For Playframework 2.2

```scala
libraryDependencies ++= Seq(
"com.nulab-inc" %% "play2-oauth2-provider" % "0.7.3"
"com.nulab-inc" %% "play2-oauth2-provider" % "0.7.4"
)
```

Expand All @@ -42,7 +42,7 @@ Add "scala-oauth2-core" instead. In this case, you need to implement your own OA

```scala
libraryDependencies ++= Seq(
"com.nulab-inc" %% "scala-oauth2-core" % "0.8.0"
"com.nulab-inc" %% "scala-oauth2-core" % "0.11.0"
)
```

Expand All @@ -57,7 +57,7 @@ case class User(id: Long, name: String, hashedPassword: String)

class MyDataHandler extends DataHandler[User] {

def validateClient(clientId: String, clientSecret: String, grantType: String): Future[Boolean] = ???
def validateClient(clientCredential: ClientCredential, grantType: String): Future[Boolean] = ???

def findUser(username: String, password: String): Future[Option[User]] = ???

Expand All @@ -71,7 +71,7 @@ class MyDataHandler extends DataHandler[User] {

def findAuthInfoByRefreshToken(refreshToken: String): Future[Option[AuthInfo[User]]] = ???

def findClientUser(clientId: String, clientSecret: String, scope: Option[String]): Future[Option[User]] = ???
def findClientUser(clientCredential: ClientCredential, scope: Option[String]): Future[Option[User]] = ???

def findAccessToken(token: String): Future[Option[AccessToken]] = ???

Expand All @@ -84,6 +84,26 @@ If your data access is blocking for the data storage, then you just wrap your im

For more details, refer to Scaladoc of ```DataHandler```.

### AuthInfo

```DataHandler``` returns ```AuthInfo``` as authorized information.
```AuthInfo``` is made up of the following fields.

```
case class AuthInfo[User](user: User, clientId: Option[String], scope: Option[String], redirectUri: Option[String])
```

- user
- ```user``` is authorized by DataHandler
- clientId
- ```clientId``` which is sent from a client has been verified by ```DataHandler```
- If your application requires client_id for client authentication, you can get ```clientId``` as below
- ```val clientId = authInfo.clientId.getOrElse(throw new InvalidClient())```
- scope
- inform the client of the scope of the access token issued
- redirectUri
- This value must be enabled on authorization code grant

### Work with Playframework

You should follow three steps below to work with Playframework.
Expand Down Expand Up @@ -126,6 +146,25 @@ object MyController extends Controller with OAuth2Provider {

If you'd like to change the OAuth workflow, modify handleRequest methods of TokenEndPoint and ```ProtectedResource``` traits.

### Customizing Grant Handlers

If you want to change which grant types are supported or to use a customized handler for a grant type, you can
override the ```handlers``` map in a customized ```TokenEndpoint``` trait since version 0.10.0. Here's an example of a customized
```TokenEndpoint``` that 1) only supports the ```password``` grant type, and 2) customizes the ``password``` grant
type handler to not require client credentials:

```scala
class MyTokenEndpoint extends TokenEndpoint {
val passwordNoCred = new Password() {
override def clientCredentialRequired = false
}

override val handlers = Map(
"password" -> passwordNoCred
)
}
```

## Examples

- [Playframework 2.2](https://github.com/oyediyildiz/scala-oauth2-provider-example)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import scalaoauth2.token.OAuth2AccessTokenResponse
*/
trait OAuth2BaseProvider extends Results {

val protectedResource = ProtectedResource
val protectedResource: ProtectedResource = ProtectedResource

val tokenEndpoint: TokenEndpoint = TokenEndpoint

Expand Down Expand Up @@ -59,7 +59,7 @@ trait OAuth2BaseProvider extends Results {
"error_description" -> e.description
)

protected[scalaoauth2] def responseOAuthErrorHeader(e: OAuthError): (String, String) = ("WWW-Authenticate" -> ("Bearer " + toOAuthErrorString(e)))
protected[scalaoauth2] def responseOAuthErrorHeader(e: OAuthError): (String, String) = "WWW-Authenticate" -> ("Bearer " + toOAuthErrorString(e))

protected def toOAuthErrorString(e: OAuthError): String = {
val params = Seq("error=\"" + e.errorType + "\"") ++
Expand Down Expand Up @@ -101,43 +101,39 @@ trait OAuth2BaseProvider extends Results {
trait OAuth2Provider extends OAuth2BaseProvider {

/**
* Issue access token in DataHandler process and return the response to client.
* Issue access token in AuthorizationHandler process and return the response to client.
*
* @param dataHandler Implemented DataHander for register access token to your system.
* @param handler Implemented AuthorizationHandler for register access token to your system.
* @param request Playframework is provided HTTP request interface.
* @tparam A play.api.mvc.Request has type.
* @return Request is successful then return JSON to client in OAuth 2.0 format.
* Request is failed then return BadRequest or Unauthorized status to client with cause into the JSON.
*/
def issueAccessToken[A, U](dataHandler: DataHandler[U], timeout: Duration = 60.seconds)(implicit request: Request[A]): Result = {
val f = tokenEndpoint.handleRequest(request, dataHandler).map { requestResult =>
requestResult match {
case Left(e) if e.statusCode == 400 => BadRequest(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
case Left(e) if e.statusCode == 401 => Unauthorized(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
case Right(r) => Ok(Json.toJson(responseAccessToken(r))).withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache")
}
def issueAccessToken[A, U](handler: AuthorizationHandler[U], timeout: Duration = 60.seconds)(implicit request: Request[A]): Result = {
val f = tokenEndpoint.handleRequest(request, handler).map {
case Left(e) if e.statusCode == 400 => BadRequest(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
case Left(e) if e.statusCode == 401 => Unauthorized(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
case Right(r) => Ok(Json.toJson(responseAccessToken(r))).withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache")
}

Await.result(f, timeout)
}

/**
* Authorize to already created access token in DataHandler process and return the response to client.
* Authorize to already created access token in ProtectedResourceHandler process and return the response to client.
*
* @param dataHandler Implemented DataHander for authenticate to your system.
* @param handler Implemented ProtectedResourceHandler for authenticate to your system.
* @param callback Callback is called when authentication is successful.
* @param request Playframework is provided HTTP request interface.
* @tparam A play.api.mvc.Request has type.
* @return Authentication is successful then the response use your API result.
* Authentication is failed then return BadRequest or Unauthorized status to client with cause into the JSON.
*/
def authorize[A, U](dataHandler: DataHandler[U], timeout: Duration = 60.seconds)(callback: AuthInfo[U] => Result)(implicit request: Request[A]): Result = {
val f = protectedResource.handleRequest(request, dataHandler).map { requestResult =>
requestResult match {
case Left(e) if e.statusCode == 400 => BadRequest.withHeaders(responseOAuthErrorHeader(e))
case Left(e) if e.statusCode == 401 => Unauthorized.withHeaders(responseOAuthErrorHeader(e))
case Right(authInfo) => callback(authInfo)
}
def authorize[A, U](handler: ProtectedResourceHandler[U], timeout: Duration = 60.seconds)(callback: AuthInfo[U] => Result)(implicit request: Request[A]): Result = {
val f = protectedResource.handleRequest(request, handler).map {
case Left(e) if e.statusCode == 400 => BadRequest.withHeaders(responseOAuthErrorHeader(e))
case Left(e) if e.statusCode == 401 => Unauthorized.withHeaders(responseOAuthErrorHeader(e))
case Right(authInfo) => callback(authInfo)
}

Await.result(f, timeout)
Expand Down Expand Up @@ -178,41 +174,37 @@ trait OAuth2Provider extends OAuth2BaseProvider {
trait OAuth2AsyncProvider extends OAuth2BaseProvider {

/**
* Issue access token in DataHandler process and return the response to client.
* Issue access token in AuthorizationHandler process and return the response to client.
*
* @param dataHandler Implemented DataHander for register access token to your system.
* @param handler Implemented AuthorizationHandler for register access token to your system.
* @param request Playframework is provided HTTP request interface.
* @tparam A play.api.mvc.Request has type.
* @return Request is successful then return JSON to client in OAuth 2.0 format.
* Request is failed then return BadRequest or Unauthorized status to client with cause into the JSON.
*/
def issueAccessToken[A, U](dataHandler: DataHandler[U])(implicit request: Request[A]): Future[Result] = {
tokenEndpoint.handleRequest(request, dataHandler).map { requestResult =>
requestResult match {
case Left(e) if e.statusCode == 400 => BadRequest(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
case Left(e) if e.statusCode == 401 => Unauthorized(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
case Right(r) => Ok(Json.toJson(responseAccessToken(r))).withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache")
}
def issueAccessToken[A, U](handler: AuthorizationHandler[U])(implicit request: Request[A]): Future[Result] = {
tokenEndpoint.handleRequest(request, handler).map {
case Left(e) if e.statusCode == 400 => BadRequest(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
case Left(e) if e.statusCode == 401 => Unauthorized(responseOAuthErrorJson(e)).withHeaders(responseOAuthErrorHeader(e))
case Right(r) => Ok(Json.toJson(responseAccessToken(r))).withHeaders("Cache-Control" -> "no-store", "Pragma" -> "no-cache")
}
}

/**
* Authorize to already created access token in DataHandler process and return the response to client.
* Authorize to already created access token in ProtectedResourceHandler process and return the response to client.
*
* @param dataHandler Implemented DataHander for authenticate to your system.
* @param handler Implemented ProtectedResourceHandler for authenticate to your system.
* @param callback Callback is called when authentication is successful.
* @param request Playframework is provided HTTP request interface.
* @tparam A play.api.mvc.Request has type.
* @return Authentication is successful then the response use your API result.
* Authentication is failed then return BadRequest or Unauthorized status to client with cause into the JSON.
*/
def authorize[A, U](dataHandler: DataHandler[U])(callback: AuthInfo[U] => Future[Result])(implicit request: Request[A]): Future[Result] = {
protectedResource.handleRequest(request, dataHandler).flatMap { requestResult =>
requestResult match {
case Left(e) if e.statusCode == 400 => Future.successful(BadRequest.withHeaders(responseOAuthErrorHeader(e)))
case Left(e) if e.statusCode == 401 => Future.successful(Unauthorized.withHeaders(responseOAuthErrorHeader(e)))
case Right(authInfo) => callback(authInfo)
}
def authorize[A, U](handler: ProtectedResourceHandler[U])(callback: AuthInfo[U] => Future[Result])(implicit request: Request[A]): Future[Result] = {
protectedResource.handleRequest(request, handler).flatMap {
case Left(e) if e.statusCode == 400 => Future.successful(BadRequest.withHeaders(responseOAuthErrorHeader(e)))
case Left(e) if e.statusCode == 401 => Future.successful(Unauthorized.withHeaders(responseOAuthErrorHeader(e)))
case Right(authInfo) => callback(authInfo)
}
}

Expand Down
27 changes: 16 additions & 11 deletions project/Build.scala
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
import sbt._
import Keys._
import ohnosequences.sbt.SbtS3Resolver._
import com.amazonaws.services.s3.model.Region

object ScalaOAuth2Build extends Build {

lazy val _organization = "kairos"
lazy val _version = "0.9.0-SNAPSHOT"
lazy val _version = "0.11.1"
lazy val _playVersion = "2.3.3"

val _crossScalaVersions = Seq("2.10.3", "2.11.2")
val _scalaVersion = "2.11.2"
val _scalaVersion = "2.10.4"

val commonDependenciesInTestScope = Seq(
"org.scalatest" %% "scalatest" % "2.2.0" % "test"

)

lazy val scalaOAuth2ProviderSettings = Defaults.defaultSettings ++ Seq(
lazy val scalaOAuth2ProviderSettings = Defaults.defaultSettings ++ S3Resolver.defaults ++ Seq(
organization := _organization,
version := _version,
scalaVersion := _scalaVersion,
credentials := Seq(Credentials(Path.userHome / ".ivy2" / "credentials")),
crossScalaVersions := _crossScalaVersions,
scalacOptions ++= _scalacOptions,
publishTo <<= version { (v: String) => _publishTo(v) },
publishMavenStyle := false,
s3region := Region.US_Standard,
publishTo := {
val prefix = if (isSnapshot.value) "snapshots" else "releases"

Some(s3resolver.value(s"$prefix s3 bucket", s3("jaroop-" + prefix)) withIvyPatterns)
},
publishArtifact in Test := false,
pomIncludeRepository := { x => false }
)
Expand All @@ -38,9 +46,11 @@ object ScalaOAuth2Build extends Build {
lazy val scalaOAuth2Core = Project(
id = "scala-oauth2-core",
base = file("scala-oauth2-core"),

settings = scalaOAuth2ProviderSettings ++ Seq(
name := "scala-oauth2-core",
description := "OAuth 2.0 server-side implementation written in Scala",

libraryDependencies ++= Seq(
"commons-codec" % "commons-codec" % "1.8",
"com.nimbusds" % "oauth2-oidc-sdk" % "3.4.1",
Expand All @@ -58,18 +68,13 @@ object ScalaOAuth2Build extends Build {
name := "play2-oauth2-provider",
description := "Support scala-oauth2-core library on Playframework Scala",
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/maven-releases/",
resolvers += "Era7 maven releases" at "http://releases.era7.com.s3.amazonaws.com",
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play" % _playVersion % "provided"
) ++ commonDependenciesInTestScope
)
) dependsOn(scalaOAuth2Core)

def _publishTo(v: String) = {
val nexus = "http://is-macmini1.cdlocal:8081/nexus/content/repositories/"
if (v.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "snapshots")
else Some("releases" at nexus + "releases")
}

val _scalacOptions = Seq("-deprecation", "-unchecked", "-feature")
}

2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.5
sbt.version=0.13.6
3 changes: 3 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resolvers += "Era7 maven releases" at "http://releases.era7.com.s3.amazonaws.com"

addSbtPlugin("ohnosequences" % "sbt-s3-resolver" % "0.11.0")
Loading