fork download
  1. package security.auth
  2.  
  3. import scala.collection.mutable.Map
  4. import scala.concurrent.Future
  5. import scala.util.{Failure, Success}
  6. import scala.util.control.NonFatal
  7. import scala.collection.JavaConversions._
  8. import scala.reflect.runtime.universe._
  9. import brix.crypto.Secret
  10. import play.api.Logger
  11. import play.api.Play.current
  12. import play.api.Play.configuration
  13. import play.api.libs.concurrent.Execution.Implicits.defaultContext
  14. import play.api.libs.json._
  15. import play.api.mvc._
  16. import play.api.mvc.Results._
  17. import play.api.http.{HeaderNames, HttpVerbs}
  18. import utils.common.ReflectHelper._
  19. import utils.common.Responses._
  20. import utils.auth.AuthErrors._
  21. import models.auth.{Token, PassToken}
  22. import models.auth.TokenType._
  23.  
  24. /**
  25.   * Represents a secured request.
  26.   *
  27.   * @constructor Initializes a new instance of the [[SecuredRequest]] class.
  28.   * @param token The JSON Web Token.
  29.   * @param request The current HTTP request.
  30.   */
  31. class SecuredRequest[A](
  32. val token: Token,
  33. request: Request[A]) extends WrappedRequest[A](request) {
  34.  
  35. import SecuredAction._
  36.  
  37. /**
  38.   * Applies `authorized` if `condition` evaluates to `true`.
  39.   *
  40.   * @param condition The function that determines whether or not the
  41.   * request is authorized.
  42.   * @param authorized The function that is applied if `condition` evaluates
  43.   * to `true`.
  44.   * @return A `Future` value containing the response to be sent to
  45.   * the client.
  46.   * @note `condition` shall return a tuple containing a Boolean
  47.   * value indicating whether or not the request is authorized,
  48.   * and optionally a `Result` to be sent to the client if
  49.   * authorization is not granted and the subject does not
  50.   * have ''admin'' privileges.
  51.   * @note `authorized` takes a Boolean paramenter indicating
  52.   * whether the action is being authorized as ''admin''.
  53.   */
  54. def authorize(
  55. condition: Token => Future[(Boolean, Option[Result])],
  56. authorized: Boolean => Future[Result]
  57. ): Future[Result] = {
  58. condition(token).flatMap { r => r._1 match {
  59. Logger.debug(s"extra condition to authorize request ${request.uri} met")
  60. authorized(false)
  61. case _ => r._2 match {
  62. case Some(simpleResult) =>
  63. Logger.debug(s"extra condition to authorize request ${request.uri} not met")
  64. Future.successful(simpleResult)
  65. case _ =>
  66. if (token.roles.contains(Role.Admin.id)) {
  67. Logger.debug(
  68. s"extra condition to authorize request ${request.uri} not met, still " +
  69. s"authorization is granted since user ${token.subject} has admin privileges"
  70. )
  71. authorized(true)
  72. } else Future.successful(refuse)
  73. }
  74. }}.recover { case e =>
  75. Logger.error("error authorizing secured request", e)
  76. InternalServerError(error(errorProcessingRequest(e.getMessage)(request)))
  77. }
  78. }
  79.  
  80. /**
  81.   * Refuses this request.
  82.   * @return A `Future` value containing the response to be sent to the client.
  83.   */
  84. def refuse = {
  85. Logger.debug(s"extra condition to authorize request ${request.uri} not met")
  86. val user = if (token.tokenType == Browse) None else Some(token.subject)
  87. Unauthorized(error(requestNotAuthorized(user)(request)))
  88. }
  89. }
  90.  
  91. /**
  92.   * Represents a secured action.
  93.   *
  94.   * @constructor Initializes a new instance of the [[SecuredAction]] class.
  95.   * @tparam T The controller type to create a secured action for.
  96.   * @param methodName The name of the method to secure.
  97.   * @param tokenTypes One or more of the [[TokenType]] values.
  98.   */
  99. class SecuredAction[T <: Controller : TypeTag] private(
  100. private val methodName: String,
  101. private val tokenTypes: Seq[TokenType]) extends ActionBuilder[SecuredRequest] {
  102.  
  103. import services.auth.AuthPlugin
  104. import SecuredAction._
  105.  
  106. def invokeBlock[A](request: Request[A], block: SecuredRequest[A] => Future[Result]) = {{
  107. if (tokenTypes.length == 1 && tokenTypes(0) == Pass) None else {
  108. request.headers.get(HeaderNames.AUTHORIZATION) match {
  109. case Some(header) => s"""$AuthScheme (.*)""".r.unapplySeq(header).map(_.head.trim)
  110. case _ => None
  111. }
  112. }} match {
  113. case Some(jwt) =>
  114. val auth = jwt.split(":") // auth(0) => token, auth(1) => signature
  115. val f = AuthPlugin.token(auth(0))
  116.  
  117. /*
  118.   * service tokens (e.g. activation, reset) are discarded immediately after they
  119.   * trigger the action they are associated with
  120.   */
  121. f.onComplete {
  122. case Success(token) => if (token.tokenType != Authorization && request.method != HttpVerbs.GET) {
  123. AuthPlugin.discardToken(token.id)
  124. }
  125. case Failure(e) => Logger.error("error deserializing token from request header", e)
  126. }
  127.  
  128. f.flatMap {
  129. case token if (!token.isValid) =>
  130. Logger.warn(s"request ${request.uri} refused: token ${token.id} has been tampered")
  131. Future.successful(Forbidden(error(authenticationViolated(token.subject)(request))))
  132. case token if (token.isExpired) =>
  133. Logger.debug(s"request ${request.uri} refused: token ${token.id} has expired")
  134. Future.successful(Forbidden(error(authenticationExpired(token.subject)(request))))
  135. case token if (!isAuthorized(token)) =>
  136. if (token.tokenType == Browse) {
  137. Logger.info(s"request ${request.uri} not authorized: anonym user does not have required privileges")
  138. Future.successful(Unauthorized(error(requestNotAuthorized(None)(request))))
  139. } else {
  140. Logger.info(s"request ${request.uri} not authorized: user ${token.subject} does not have required privileges")
  141. Future.successful(Unauthorized(error(requestNotAuthorized(Some(token.subject))(request))))
  142. }
  143. case token => if (token.apiKey == Token.BootstrapKey) {
  144. Logger.debug(s"request ${request.uri} authorized for user ${token.subject}")
  145. block(new SecuredRequest(token, request))
  146. } else {
  147. val body = request.body match {
  148. case json: JsValue => json.toString
  149. case _ => ""
  150. }
  151. Secret(token.apiKey).sign(auth(0) + request.method + request.uri + body) match {
  152. case Success(signed) => if (signed == auth(1)) {
  153. if (token.tokenType == Browse) {
  154. Logger.debug(s"request ${request.uri} authorized for anonym user")
  155. } else {
  156. Logger.debug(s"request ${request.uri} authorized for user ${token.subject}")
  157. }
  158. block(new SecuredRequest(token, request))
  159. } else {
  160. Logger.warn(s"request ${request.uri} refused: request has been tampered")
  161. Future.successful(Forbidden(error(requestViolated()(request))))
  162. }
  163. case Failure(e) => Future.failed(e)
  164. }
  165. }
  166. }.recover {
  167. case e: java.text.ParseException =>
  168. Logger.error("error deserializing token from request header", e)
  169. BadRequest(error(errorDeserializingToken(e.getMessage)(request)))
  170. case NonFatal(e) =>
  171. Logger.error("error processing secured request", e)
  172. InternalServerError(error(errorProcessingRequest(e.getMessage)(request)))
  173. }
  174. case _ => {
  175. if (tokenTypes.contains(Pass)) {
  176. Logger.debug(s"request ${request.uri} authorized for anonym user")
  177. block(new SecuredRequest(PassToken()(request), request))
  178. } else {
  179. Logger.debug(s"request ${request.uri} not authorized")
  180. Future.successful(Unauthorized(
  181. error(requestNotAuthenticated()(request))
  182. ).withHeaders(HeaderNames.WWW_AUTHENTICATE -> AuthScheme))
  183. }
  184. }
  185. }
  186. }
  187.  
  188. /**
  189.   * Returns a Boolean value indicating whether or not this
  190.   * action is authorized.
  191.   *
  192.   * @param token The authorization JSON Web Token.
  193.   * @return `true` if `token` defines the claims that authorize
  194.   * this action; otherwise, `false`.
  195.   */
  196. private def isAuthorized(token: Token) = {
  197. val roles = securityProfiles(operationNickname[T])
  198. tokenTypes.contains(token.tokenType) && (
  199. roles.contains(Role.id("any")) || // action does not require any specific privileges
  200. token.roles.contains(Role.Admin.id) || // current subject has admin privileges
  201. token.roles.exists(roles.contains(_)) // current subject has required privileges
  202. )
  203. }
  204.  
  205. /**
  206.   * Returns the fully qualified nickname of the method that handles
  207.   * the current HTTP request.
  208.   *
  209.   * @tparam T The type of the current `Controller`.
  210.   * @return The fully qualified nickname of the method that handles
  211.   * the current HTTP request.
  212.   */
  213. private def operationNickname[T <: Controller : TypeTag] = {
  214. val clazzAnnotations = classAnnotations[T]
  215. val api = clazzAnnotations(
  216. "Api" )( "value"
  217. ).asInstanceOf[LiteralArgument].value.value.asInstanceOf[String]
  218.  
  219. val functionAnnotations = methodAnnotations[T]
  220. val nickname = functionAnnotations(methodName)(
  221. "ApiOperation" )( "nickname"
  222. ).asInstanceOf[LiteralArgument].value.value.asInstanceOf[String]
  223.  
  224. s"$api/$nickname"
  225. }
  226. }
  227.  
  228. /**
  229.   * Factory class for creating [[SecuredAction]] instances.
  230.   */
  231. object SecuredAction {
  232.  
  233. final val AuthScheme = "Gok!llo"
  234. private var securityProfiles = Map[String, List[Int]]().withDefaultValue(List.empty)
  235.  
  236. configuration.getConfigList("auth.securityProfiles").map { _.toList.map { config =>
  237. config.getString("operation").map { operation =>
  238. securityProfiles += (operation -> config.getStringList("roles").map {
  239. _.toList.map(Role.id(_))
  240. }.getOrElse(List.empty))
  241. }
  242. }}
  243.  
  244. /**
  245.   * Initializes a new instance of the [[SecuredAction]] class.
  246.   *
  247.   * @tparam T The controller type to create a secured action for.
  248.   * @param methodName The name of the method to secure.
  249.   */
  250. def apply[T <: Controller : TypeTag](
  251. methodName: String
  252. ): SecuredAction[T] = apply(methodName, Authorization)
  253.  
  254. /**
  255.   * Initializes a new instance of the [[SecuredAction]] class.
  256.   *
  257.   * @tparam T The controller type to create a secured action for.
  258.   * @param methodName The name of the method to secure.
  259.   * @param tokenTypes One or more of the [[TokenType]] values.
  260.   */
  261. def apply[T <: Controller : TypeTag](
  262. methodName: String,
  263. tokenTypes: TokenType*
  264. ): SecuredAction[T] = new SecuredAction[T](methodName, tokenTypes)
  265. }
Compilation error #stdin compilation error #stdout 0s 0KB
stdin
Standard input is empty
compilation info
/opt/scala/bin/scalac: line 50: /dev/null: Permission denied
Main.scala:9: error: not found: object brix
import brix.crypto.Secret
       ^
Main.scala:10: error: not found: object play
import play.api.Logger
       ^
Main.scala:11: error: not found: object play
import play.api.Play.current
       ^
Main.scala:12: error: not found: object play
import play.api.Play.configuration
       ^
Main.scala:13: error: not found: object play
import play.api.libs.concurrent.Execution.Implicits.defaultContext
       ^
Main.scala:14: error: not found: object play
import play.api.libs.json._
       ^
Main.scala:15: error: not found: object play
import play.api.mvc._
       ^
Main.scala:16: error: not found: object play
import play.api.mvc.Results._
       ^
Main.scala:17: error: not found: object play
import play.api.http.{HeaderNames, HttpVerbs}
       ^
Main.scala:18: error: not found: object utils
import utils.common.ReflectHelper._
       ^
Main.scala:19: error: not found: object utils
import utils.common.Responses._
       ^
Main.scala:20: error: not found: object utils
import utils.auth.AuthErrors._
       ^
Main.scala:21: error: not found: object models
import models.auth.{Token, PassToken}
       ^
Main.scala:22: error: not found: object models
import models.auth.TokenType._
       ^
Main.scala:33: error: not found: type WrappedRequest
  request: Request[A]) extends WrappedRequest[A](request) {
                               ^
Main.scala:32: error: not found: type Token
  val token: Token,
             ^
Main.scala:33: error: not found: type Request
  request: Request[A]) extends WrappedRequest[A](request) {
           ^
Main.scala:31: error: too many arguments for constructor Object: ()Object
class SecuredRequest[A](
                       ^
Main.scala:57: error: not found: type Result
  ): Future[Result] = {
            ^
Main.scala:55: error: not found: type Token
    condition: Token => Future[(Boolean, Option[Result])],
               ^
Main.scala:55: error: not found: type Result
    condition: Token => Future[(Boolean, Option[Result])],
                                                ^
Main.scala:56: error: not found: type Result
    authorized: Boolean => Future[Result]
                                  ^
Main.scala:86: error: not found: value Logger
    Logger.debug(s"extra condition to authorize request ${request.uri} not met")
    ^
Main.scala:88: error: not found: value Unauthorized
    Unauthorized(error(requestNotAuthorized(user)(request)))
    ^
Main.scala:60: error: not found: value Logger
        Logger.debug(s"extra condition to authorize request ${request.uri} met")
        ^
Main.scala:102: error: not found: type ActionBuilder
  private val tokenTypes: Seq[TokenType]) extends ActionBuilder[SecuredRequest] {
                                                  ^
Main.scala:100: error: not found: type Controller
class SecuredAction[T <: Controller : TypeTag] private(
                         ^
Main.scala:102: error: not found: type TokenType
  private val tokenTypes: Seq[TokenType]) extends ActionBuilder[SecuredRequest] {
                              ^
Main.scala:104: error: not found: value services
  import services.auth.AuthPlugin
         ^
Main.scala:107: error: not found: type Request
  def invokeBlock[A](request: Request[A], block: SecuredRequest[A] => Future[Result]) = {{
                              ^
Main.scala:116: error: not found: value AuthPlugin
        val f = AuthPlugin.token(auth(0))
                ^
Main.scala:176: error: not found: value Pass
        if (tokenTypes.contains(Pass)) {
                                ^
Main.scala:177: error: not found: value Logger
          Logger.debug(s"request ${request.uri} authorized for anonym user")
          ^
Main.scala:107: error: not found: type Result
  def invokeBlock[A](request: Request[A], block: SecuredRequest[A] => Future[Result]) = {{
                                                                             ^
Main.scala:178: error: not found: value PassToken
          block(new SecuredRequest(PassToken()(request), request))
                                   ^
Main.scala:180: error: not found: value Logger
          Logger.debug(s"request ${request.uri} not authorized")
          ^
Main.scala:181: error: not found: value Unauthorized
          Future.successful(Unauthorized(
                            ^
Main.scala:215: error: not found: value classAnnotations
    val clazzAnnotations = classAnnotations[T]
                           ^
Main.scala:214: error: not found: type Controller
  private def operationNickname[T <: Controller : TypeTag] = {
                                     ^
Main.scala:220: error: not found: value methodAnnotations
    val functionAnnotations = methodAnnotations[T]
                              ^
Main.scala:197: error: not found: type Token
  private def isAuthorized(token: Token) = {
                                  ^
Main.scala:200: error: not found: value Role
      roles.contains(Role.id("any")) ||       // action does not require any specific privileges
                     ^
Main.scala:237: error: not found: value configuration
  configuration.getConfigList("auth.securityProfiles").map { _.toList.map { config =>
  ^
Main.scala:251: error: not found: type Controller
  def apply[T <: Controller : TypeTag](
                 ^
Main.scala:262: error: not found: type Controller
  def apply[T <: Controller : TypeTag](
                 ^
Main.scala:264: error: not found: type TokenType
    tokenTypes: TokenType*
                ^
Main.scala:253: error: not found: value Authorization
  ): SecuredAction[T] = apply(methodName, Authorization)
                                          ^
47 errors found
spoj: The program compiled successfully, but Main.class was not found.
      Class Main should contain method: def main(args: Array[String]).
stdout
Standard output is empty