Skip to content
This repository has been archived by the owner on Sep 12, 2021. It is now read-only.

Commit

Permalink
Merge 958d3a9 into 82af259
Browse files Browse the repository at this point in the history
  • Loading branch information
akkie committed Dec 22, 2014
2 parents 82af259 + 958d3a9 commit 349f960
Show file tree
Hide file tree
Showing 11 changed files with 550 additions and 89 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Add request handlers
- Add request providers in combination with HTTP basic auth provider
- Add Dropbox provider
- Add request extractors

## 1.0 (2014-06-12)

Expand Down
7 changes: 4 additions & 3 deletions app/com/mohiva/play/silhouette/api/Provider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
package com.mohiva.play.silhouette.api

import play.api.mvc.RequestHeader
import play.api.mvc.Request

import scala.concurrent.Future

Expand Down Expand Up @@ -50,8 +50,9 @@ trait RequestProvider extends Provider {
* Some(identity) - If returning some identity, then this provider will be used for authentication.
* Exception - Throwing an exception breaks the chain. The error handler will also handle this exception.
*
* @param request The request header.
* @param request The request.
* @tparam B The type of the body.
* @return Some login info on successful authentication or None if the authentication was unsuccessful.
*/
def authenticate(request: RequestHeader): Future[Option[LoginInfo]]
def authenticate[B](request: Request[B]): Future[Option[LoginInfo]]
}
10 changes: 6 additions & 4 deletions app/com/mohiva/play/silhouette/api/Silhouette.scala
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,12 @@ trait Silhouette[I <: Identity, A <: Authenticator] extends Controller with Logg
* left and new authenticators on the right. All new authenticators must be initialized later in the flow,
* with the result returned from the invoked block.
*
* @param request The current request header.
* @param request The current request.
* @tparam B The type of the request body.
* @return A tuple which consists of (maybe the existing authenticator on the left or a
* new authenticator on the right -> maybe the identity).
*/
protected def handleAuthentication(implicit request: RequestHeader): Future[(Option[Either[A, A]], Option[I])] = {
protected def handleAuthentication[B](implicit request: Request[B]): Future[(Option[Either[A, A]], Option[I])] = {
env.authenticatorService.retrieve.flatMap {
// A valid authenticator was found so we retrieve also the identity
case Some(a) if a.isValid => env.identityService.retrieve(a.loginInfo).map(i => Some(Left(a)) -> i)
Expand Down Expand Up @@ -303,10 +304,11 @@ trait Silhouette[I <: Identity, A <: Authenticator] extends Controller with Logg
* it tries to authenticate until one provider returns an identity. The order of the providers
* isn't guaranteed.
*
* @param request The current request header.
* @param request The current request.
* @tparam B The type of the request body.
* @return Some identity or None if authentication was not successful.
*/
private def handleRequestProviderAuthentication(implicit request: RequestHeader): Future[Option[LoginInfo]] = {
private def handleRequestProviderAuthentication[B](implicit request: Request[B]): Future[Option[LoginInfo]] = {
def auth(providers: Seq[RequestProvider]): Future[Option[LoginInfo]] = {
providers match {
case Nil => Future.successful(None)
Expand Down
214 changes: 214 additions & 0 deletions app/com/mohiva/play/silhouette/api/util/RequestExtractor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package com.mohiva.play.silhouette.api.util

import com.mohiva.play.silhouette.api.Logger
import play.api.libs.json.JsValue
import play.api.mvc._

import scala.language.implicitConversions
import scala.xml.NodeSeq

/**
* Adds the ability to extract values from a request.
*/
trait RequestExtractor[-B] extends Logger {

/**
* Extracts a string from a request.
*
* @param name The name of the value to extract.
* @param request The request from which the value should be extract.
* @return The extracted value as string.
*/
def extractString(name: String)(implicit request: Request[B]): Option[String]

/**
* Extracts a value from query string.
*
* @param name The name of the value to extract.
* @param request The request from which the value should be extract.
* @return The extracted value as string.
*/
protected def fromQueryString(name: String)(implicit request: Request[B]): Option[String] = {
logger.debug("[Silhouette] Try to extract value with name `%s` from query string: %s".format(name, request.rawQueryString))
request.queryString.get(name).flatMap(_.headOption)
}

/**
* Extracts a value from form url encoded body.
*
* @param name The name of the value to extract.
* @param body The body from which the value should be extract.
* @return The extracted value as string.
*/
protected def fromFormUrlEncoded(name: String, body: Map[String, Seq[String]]): Option[String] = {
logger.debug("[Silhouette] Try to extract value with name `%s` from form url encoded body: %s".format(name, body))
body.get(name).flatMap(_.headOption)
}

/**
* Extracts a value from Json body.
*
* @param name The name of the value to extract.
* @param body The body from which the value should be extract.
* @return The extracted value.
*/
protected def fromJson(name: String, body: JsValue): Option[String] = {
logger.debug("[Silhouette] Try to extract value with name `%s` from Json body: %s".format(name, body))
body.\(name).asOpt[String]
}

/**
* Extracts a value from Xml body.
*
* @param name The name of the value to extract.
* @param body The body from which the value should be extract.
* @return The extracted value.
*/
protected def fromXml(name: String, body: NodeSeq): Option[String] = {
logger.debug("[Silhouette] Try to extract value with name `%s` from Xml body: %s".format(name, body))
body.\\(name).headOption.map(_.text)
}
}

/**
* The companion object.
*/
object RequestExtractor extends DefaultRequestExtractors

/**
* Default request extractors with lower priority.
*/
trait LowPriorityRequestExtractors {

/**
* Tries to extract the value from query string.
*
* This acts as a catch all request extractor to avoid errors for not implemented body types.
*/
implicit def anyExtractor[B]: RequestExtractor[B] = new RequestExtractor[B] {
def extractString(name: String)(implicit request: Request[B]) = {
fromQueryString(name)
}
}
}

/**
* Contains the default request extractors.
*/
trait DefaultRequestExtractors extends LowPriorityRequestExtractors {

/**
* Tries to extract the value from query string and then from form url encoded body.
*/
implicit val anyContentAsFormUrlEncodedExtractor = new RequestExtractor[AnyContentAsFormUrlEncoded] {
def extractString(name: String)(implicit request: Request[AnyContentAsFormUrlEncoded]) = {
fromQueryString(name).orElse(fromFormUrlEncoded(name, request.body.data))
}
}

/**
* Tries to extract the value from query string and then from Json body.
*/
implicit val anyContentAsJsonExtractor = new RequestExtractor[AnyContentAsJson] {
def extractString(name: String)(implicit request: Request[AnyContentAsJson]) = {
fromQueryString(name).orElse(fromJson(name, request.body.json))
}
}

/**
* Tries to extract the value from query string and then from Xml body.
*/
implicit val anyContentAsXmlExtractor = new RequestExtractor[AnyContentAsXml] {
def extractString(name: String)(implicit request: Request[AnyContentAsXml]) = {
fromQueryString(name).orElse(fromXml(name, request.body.xml))
}
}

/**
* Tries to extract the value from query string and then from form url encoded body.
*/
implicit val formUrlEncodedExtractor = new RequestExtractor[Map[String, Seq[String]]] {
def extractString(name: String)(implicit request: Request[Map[String, Seq[String]]]) = {
fromQueryString(name).orElse(fromFormUrlEncoded(name, request.body))
}
}

/**
* Tries to extract the value from query string and then from Json body.
*/
implicit val jsonExtractor = new RequestExtractor[JsValue] {
def extractString(name: String)(implicit request: Request[JsValue]) = {
fromQueryString(name).orElse(fromJson(name, request.body))
}
}

/**
* Tries to extract the value from query string and then from Xml body.
*/
implicit val xmlExtractor = new RequestExtractor[NodeSeq] {
def extractString(name: String)(implicit request: Request[NodeSeq]) = {
fromQueryString(name).orElse(fromXml(name, request.body))
}
}
}

/**
* A request which can extract values based on the request body.
*
* @param request The request.
* @param extractor The extractor to extract the value.
* @tparam B The type of the request body.
*/
class ExtractableRequest[B](request: Request[B])(implicit extractor: RequestExtractor[B])
extends WrappedRequest[B](request) {

/**
* Extracts a string from a request.
*
* @param name The name of the value to extract.
* @return The extracted value as string.
*/
def extractString(name: String): Option[String] = extractor.extractString(name)(request)
}

/**
* The companion object.
*/
object ExtractableRequest {

/**
* Creates an extractable request from an implicit request.
*
* @param request The implicit request.
* @param extractor The extractor to extract the value.
* @tparam B The type of the request body.
* @return An extractable request.
*/
def apply[B](implicit request: Request[B], extractor: RequestExtractor[B]) = {
new ExtractableRequest(request)
}

/**
* Converts a `Request` to a `ExtractableRequest` instance.
*
* @param request The request to convert.
* @param extractor The extractor to extract the value.
* @tparam B The type of the request body.
* @return An extractable request.
*/
implicit def convertExplicit[B](request: Request[B])(implicit extractor: RequestExtractor[B]): ExtractableRequest[B] = {
new ExtractableRequest(request)
}

/**
* Converts an implicit `Request` to a `ExtractableRequest` instance.
*
* @param request The request to convert.
* @param extractor The extractor to extract the value.
* @tparam B The type of the request body.
* @return An extractable request.
*/
implicit def convertImplicit[B](implicit request: Request[B], extractor: RequestExtractor[B]): ExtractableRequest[B] = {
this(request, extractor)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ package com.mohiva.play.silhouette.impl.providers

import com.mohiva.play.silhouette.api.exceptions.{ AccessDeniedException, AuthenticationException }
import com.mohiva.play.silhouette.api.services.AuthInfoService
import com.mohiva.play.silhouette.api.util.{ Base64, Credentials, PasswordHasher, PasswordInfo }
import com.mohiva.play.silhouette.api.util._
import com.mohiva.play.silhouette.api.{ LoginInfo, RequestProvider }
import com.mohiva.play.silhouette.impl.providers.BasicAuthProvider._
import play.api.http.HeaderNames
import play.api.libs.concurrent.Execution.Implicits._
import play.api.mvc.RequestHeader
import play.api.mvc.{ Request, RequestHeader }

import scala.concurrent.Future

Expand Down Expand Up @@ -54,10 +54,11 @@ class BasicAuthProvider(
/**
* Authenticates an identity based on credentials sent in a request.
*
* @param request The request header.
* @param request The request.
* @tparam B The type of the body.
* @return Some login info on successful authentication or None if the authentication was unsuccessful.
*/
def authenticate(request: RequestHeader): Future[Option[LoginInfo]] = {
def authenticate[B](request: Request[B]): Future[Option[LoginInfo]] = {
getCredentials(request) match {
case Some(credentials) =>
val loginInfo = LoginInfo(id, credentials.identifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import java.util.UUID
import com.mohiva.play.silhouette.api._
import com.mohiva.play.silhouette.api.exceptions._
import com.mohiva.play.silhouette.api.services.AuthInfo
import com.mohiva.play.silhouette.api.util.{ CacheLayer, HTTPLayer }
import com.mohiva.play.silhouette.api.util.{ CacheLayer, ExtractableRequest, HTTPLayer }
import com.mohiva.play.silhouette.impl.providers.OAuth1Provider._
import play.api.libs.concurrent.Execution.Implicits._
import play.api.libs.ws.WSSignatureCalculator
import play.api.mvc.{ Result, RequestHeader, Results }
import play.api.mvc._

import scala.concurrent.Future

Expand All @@ -54,19 +54,18 @@ abstract class OAuth1Provider(
/**
* Starts the authentication process.
*
* @param request The request header.
* @param request The request.
* @return Either a Result or the auth info from the provider.
*/
def authenticate()(implicit request: RequestHeader): Future[Either[Result, OAuth1Info]] = {
logger.debug("[Silhouette][%s] Query string: %s".format(id, request.rawQueryString))
request.queryString.get(Denied) match {
def authenticate[B]()(implicit request: ExtractableRequest[B]): Future[Either[Result, OAuth1Info]] = {
request.extractString(Denied) match {
case Some(_) => Future.failed(new AccessDeniedException(AuthorizationError.format(id, Denied)))
case None => request.queryString.get(OAuthVerifier) match {
case None => request.extractString(OAuthVerifier) match {
// Second step in the OAuth flow.
// We have the request info in the cache, and we need to swap it for the access info.
case Some(seq) => cachedInfo.flatMap {
case Some(verifier) => cachedInfo.flatMap {
case (cacheID, cachedInfo) =>
service.retrieveAccessToken(cachedInfo, seq.head).map { info =>
service.retrieveAccessToken(cachedInfo, verifier).map { info =>
cacheLayer.remove(cacheID)
Right(info)
}.recover {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,17 @@ package com.mohiva.play.silhouette.impl.providers

import java.net.URLEncoder._

import com.mohiva.play.silhouette._
import com.mohiva.play.silhouette.api._
import com.mohiva.play.silhouette.api.exceptions._
import com.mohiva.play.silhouette.api.services.AuthInfo
import com.mohiva.play.silhouette.api.util.HTTPLayer
import com.mohiva.play.silhouette.impl
import com.mohiva.play.silhouette.api.util.{ ExtractableRequest, HTTPLayer }
import com.mohiva.play.silhouette.{ impl, _ }
import com.mohiva.play.silhouette.impl.providers.OAuth2Provider._
import play.api.libs.concurrent.Execution.Implicits._
import play.api.libs.functional.syntax._
import play.api.libs.json._
import play.api.libs.ws.WSResponse
import play.api.mvc.{ RequestHeader, Result, Results }
import play.api.mvc._

import scala.concurrent.Future
import scala.util.{ Failure, Success, Try }
Expand Down Expand Up @@ -91,17 +90,16 @@ abstract class OAuth2Provider(httpLayer: HTTPLayer, stateProvider: OAuth2StatePr
/**
* Starts the authentication process.
*
* @param request The request header.
* @param request The request.
* @return Either a Result or the auth info from the provider.
*/
def authenticate()(implicit request: RequestHeader): Future[Either[Result, OAuth2Info]] = {
logger.debug("[Silhouette][%s] Query string: %s".format(id, request.rawQueryString))
request.queryString.get(Error).flatMap(_.headOption).map {
def authenticate[B]()(implicit request: ExtractableRequest[B]): Future[Either[Result, OAuth2Info]] = {
request.extractString(Error).map {
case e @ AccessDenied => new AccessDeniedException(AuthorizationError.format(id, e))
case e => new AuthenticationException(AuthorizationError.format(id, e))
} match {
case Some(throwable) => Future.failed(throwable)
case None => request.queryString.get(Code).flatMap(_.headOption) match {
case None => request.extractString(Code) match {
// We're being redirected back from the authorization server with the access code
case Some(code) => stateProvider.validate(id).recoverWith {
case e => Future.failed(new AuthenticationException(InvalidState.format(id), e))
Expand Down
Loading

0 comments on commit 349f960

Please sign in to comment.