Skip to content

Commit

Permalink
Merge pull request #185 from Viva-con-Agua/develop
Browse files Browse the repository at this point in the history
Integrate #183 and #184
  • Loading branch information
johannsell authored Jan 25, 2018
2 parents 8f2871f + 041b1c5 commit a5cc2ab
Show file tree
Hide file tree
Showing 15 changed files with 173 additions and 41 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,9 @@ and your service.
ChangeLog
=========

## Version 0.20.14 (2018-01-09)
## Version 0.21.15 (2018-01-25)
* [[I] #183 - Accessibility of views bases on Pool 1 connection](https://github.com/Viva-con-Agua/drops/issues/183)
* [[F] #180 - Allow webservice secrets](https://github.com/Viva-con-Agua/drops/issues/180)
* [[F] #106 - Send new registered user to Pool 1](https://github.com/Viva-con-Agua/drops/issues/106)

## Version 0.19.14 (2017-12-14)
Expand Down
8 changes: 4 additions & 4 deletions app/api/ApiAction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ class ApiAction @Inject()(
def invokeBlock[A](request: Request[A], block: (ApiRequest[A]) => Future[Result]) = {
implicit val messages : Messages = messagesApi.preferred(request)
Try(apiRequestProvider.get[A](request)) match {
case Success(apiRequest) => block(apiRequest)/*apiRequest.getClient.flatMap(_ match {
case Some(oauthClient) => block(apiRequest)
case _ => Future.successful(BadRequest(Json.obj("error" -> Messages("rest.api.noValidAPIClient"))))
})*/
case Success(apiRequest) => apiRequest.getClient.flatMap(_ match {
case Left(oauthClient) => block(apiRequest)
case Right(e) => Future.successful(BadRequest(Json.obj("error" -> Messages(e.getMessage))))
})
case Failure(f) => Future.successful(BadRequest(Json.obj("error" -> Messages("rest.api.noValidAPIRequest", f.getMessage))))
}
}
Expand Down
37 changes: 27 additions & 10 deletions app/api/ApiRequest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,31 @@ import javax.inject.Inject
import api.query._
import daos.{OauthClientDao, UserDao}
import models.OauthClient
import play.api.Configuration
import play.api.libs.json._
import play.api.mvc.Request
import play.api.mvc.AnyContent
import scala.concurrent.ExecutionContext.Implicits.global

import scala.concurrent.Future
import scala.util.{Failure, Success, Try}


class ApiRequestProvider @Inject() (
configuration: Configuration,
oauthClientDao : OauthClientDao,
userDao: UserDao
) {
def get[A](request: Request[A]) = ApiRequest[A](request, oauthClientDao, userDao)
def get[A](request: Request[A]) = ApiRequest[A](request, oauthClientDao, userDao, configuration)
}
/**
* Created by johann on 21.12.16.
*/
case class ApiRequest[A](request : Request[A], oauthClientDao : OauthClientDao, userDao: UserDao){
// val cĺientId = request.queryString("client_id").headOption.getOrElse(
// throw new Exception // Todo: Meaningful Exception
// )
// val clientSecret = request.queryString("client_secret").headOption.getOrElse(
// throw new Exception // Todo: Meaningful Exception
// )
case class ApiRequest[A](request : Request[A], oauthClientDao : OauthClientDao, userDao: UserDao, config: Configuration){
val cĺientId = request.queryString("client_id").headOption.getOrElse(
throw new Exception // Todo: Meaningful Exception
)
val clientSecret = request.queryString("client_secret").headOption
val version = request.queryString.getOrElse("version",
request.queryString.getOrElse("v",
Seq("1.1.0") // change this, if a new version of the webservice was implemented (so it uses the new version by default)
Expand Down Expand Up @@ -57,6 +58,22 @@ case class ApiRequest[A](request : Request[A], oauthClientDao : OauthClientDao,
case _ => None // all other possible contents are unknown to me
}

// def getClient : Future[Option[OauthClient]] =
// oauthClientDao.find(cĺientId, clientSecret)
def getClient : Future[Either[OauthClient, Exception]] =
config.getString("drops.ws.security").getOrElse("none") match {
case "none" => oauthClientDao.find(cĺientId).map(_ match {
case Some(client) => Left(client)
case _ => Right(new Exception("rest.api.givenClientNotFound"))
})
case "secret" => clientSecret match {
case Some(secret) => oauthClientDao.find(cĺientId, secret).map(_ match {
case Some(client) => Left(client)
case _ => Right(new Exception("rest.api.givenClientNotFound"))
})
case _ => Future.successful(Right(new Exception("rest.api.noClientSecretGiven")))
}
case "sluice" => {
// Todo: Implement integration for using sluice in intra-microservice communication
Future.successful(Right(new Exception("rest.api.securityMethodNotImplemented")))
}
}
}
26 changes: 14 additions & 12 deletions app/controllers/Application.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import services.UserService
import daos.{CrewDao, OauthClientDao, TaskDao}
import play.api.libs.json.{JsPath, JsValue, Json, Reads}
import play.api.libs.ws._
import utils.{WithAlternativeRoles, WithRole}
import utils.authorization.{Pool1Restriction, WithAlternativeRoles, WithRole}

import scala.collection.JavaConversions._
import scala.concurrent.ExecutionContext.Implicits.global
Expand All @@ -36,17 +36,19 @@ class Application @Inject() (
configuration: Configuration,
socialProviderRegistry: SocialProviderRegistry) extends Silhouette[User,CookieAuthenticator] {

def index = SecuredAction.async { implicit request =>
val pool1Export = configuration.getBoolean("pool1.export").getOrElse(false)

def index = SecuredAction(Pool1Restriction(pool1Export)).async { implicit request =>
Future.successful(Ok(views.html.index(request.identity, request.authenticator.loginInfo)))
}

def profile = SecuredAction.async { implicit request =>
def profile = SecuredAction(Pool1Restriction(pool1Export)).async { implicit request =>
crewDao.list.map(l =>
Ok(views.html.profile(request.identity, request.authenticator.loginInfo, socialProviderRegistry, UserForms.userForm, CrewForms.geoForm, l.toSet, PillarForms.define))
)
}

def updateBase = SecuredAction.async { implicit request =>
def updateBase = SecuredAction(Pool1Restriction(pool1Export)).async { implicit request =>
UserForms.userForm.bindFromRequest.fold(
bogusForm => crewDao.list.map(l => BadRequest(views.html.profile(request.identity, request.authenticator.loginInfo, socialProviderRegistry, bogusForm, CrewForms.geoForm, l.toSet, PillarForms.define))),
userData => request.identity.profileFor(request.authenticator.loginInfo) match {
Expand All @@ -67,7 +69,7 @@ class Application @Inject() (
)
}

def updateCrew = SecuredAction.async { implicit request =>
def updateCrew = SecuredAction(Pool1Restriction(pool1Export)).async { implicit request =>
CrewForms.geoForm.bindFromRequest.fold(
bogusForm => crewDao.list.map(l => BadRequest(views.html.profile(request.identity, request.authenticator.loginInfo, socialProviderRegistry, UserForms.userForm, bogusForm, l.toSet, PillarForms.define))),
crewData => {
Expand All @@ -90,7 +92,7 @@ class Application @Inject() (
)
}

def updatePillar = SecuredAction.async { implicit request =>
def updatePillar = SecuredAction(Pool1Restriction(pool1Export)).async { implicit request =>
PillarForms.define.bindFromRequest.fold(
bogusForm => crewDao.list.map(l => BadRequest(views.html.profile(request.identity, request.authenticator.loginInfo, socialProviderRegistry, UserForms.userForm, CrewForms.geoForm, l.toSet, bogusForm))),
pillarData => request.identity.profileFor(request.authenticator.loginInfo) match {
Expand All @@ -108,12 +110,12 @@ class Application @Inject() (
)
}

def task = SecuredAction{ implicit request =>
def task = SecuredAction(Pool1Restriction(pool1Export)) { implicit request =>
val resultingTasks: Future[Seq[Task]] = taskDao.all()
Ok(views.html task(request.identity, request.authenticator.loginInfo, resultingTasks))
}

def initCrews = Action.async { request =>
def initCrews = SecuredAction(WithRole(RoleAdmin) && Pool1Restriction(pool1Export)).async { request =>
configuration.getConfigList("crews").map(_.toList.map(c =>
crewDao.find(c.getString("name").get).map(_ match {
case Some(crew) => crew
Expand All @@ -125,7 +127,7 @@ class Application @Inject() (
Future.successful(Redirect("/"))
}

def fixCrewsID = SecuredAction.async { request =>
def fixCrewsID = SecuredAction(WithRole(RoleAdmin) && Pool1Restriction(pool1Export)).async { request =>
val crews = crewDao.listOfStubs.flatMap(l => Future.sequence(l.map(oldCrew => crewDao.update(oldCrew.toCrew))))
val users = crews.flatMap(l => userService.listOfStubs.flatMap(ul => Future.sequence(ul.map(user => {
val profiles = user.profiles.map(profile => {
Expand All @@ -141,7 +143,7 @@ class Application @Inject() (
res.map(pair => Ok(Json.arr(Json.toJson(pair._1), Json.toJson(pair._2))))
}

def initUsers(number: Int, specialRoleUsers : Int = 1) = SecuredAction(WithRole(RoleAdmin)).async { request => {
def initUsers(number: Int, specialRoleUsers : Int = 1) = SecuredAction(WithRole(RoleAdmin) && Pool1Restriction(pool1Export)).async { request => {
val wsRequest = ws.url("https://randomuser.me/api/")
.withHeaders("Accept" -> "application/json")
.withRequestTimeout(10000)
Expand Down Expand Up @@ -173,11 +175,11 @@ class Application @Inject() (
)
}}

def registration = SecuredAction(WithAlternativeRoles(RoleAdmin, RoleEmployee)) { implicit request =>
def registration = SecuredAction((WithRole(RoleAdmin) || WithRole(RoleEmployee)) && Pool1Restriction(pool1Export)) { implicit request =>
Ok(views.html.oauth2.register(request.identity, request.authenticator.loginInfo, socialProviderRegistry, OAuth2ClientForms.register))
}

def registerOAuth2Client = SecuredAction(WithAlternativeRoles(RoleAdmin, RoleEmployee)).async { implicit request =>
def registerOAuth2Client = SecuredAction((WithRole(RoleAdmin) || WithRole(RoleEmployee)) && Pool1Restriction(pool1Export)).async { implicit request =>
OAuth2ClientForms.register.bindFromRequest.fold(
bogusForm => Future.successful(BadRequest(views.html.oauth2.register(request.identity, request.authenticator.loginInfo, socialProviderRegistry, bogusForm))),
registerData => {
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/Files.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import models._
import play.modules.reactivemongo.json.collection.JSONCollection
import services.UserService
import reactivemongo.api.gridfs.{DefaultFileToSave, FileToSave, GridFS, ReadFile}
import utils.authorization.Pool1Restriction

import scala.collection.JavaConversions._
import scala.concurrent.ExecutionContext.Implicits.global
Expand All @@ -31,6 +32,9 @@ class Files @Inject() (
configuration: Configuration,
val reactiveMongoApi: ReactiveMongoApi
) extends Silhouette[User,CookieAuthenticator] with MongoController with ReactiveMongoComponents {

val pool1Export = configuration.getBoolean("pool1.export").getOrElse(false)

// gridFSBodyParser from `MongoController`
import MongoController.readFileReads

Expand All @@ -47,7 +51,7 @@ class Files @Inject() (

val files = reactiveMongoApi.db.collection[JSONCollection]("fs.files")

def uploadProfileImage = SecuredAction.async(fsParser) { implicit request =>
def uploadProfileImage = SecuredAction(Pool1Restriction(pool1Export)).async(fsParser) { implicit request =>
val futureFile: Future[ReadFile[JSONSerializationPack.type, JsValue]] =
request.body.files.head.ref

Expand Down
33 changes: 29 additions & 4 deletions app/controllers/OAuth2Controller.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class OAuth2Controller @Inject() (
oauthClientDao: OauthClientDao,
oauthDataHandler: OAuthDataHandler,
val messagesApi: MessagesApi,
configuration: Configuration,
val env:Environment[User,CookieAuthenticator]
) extends Silhouette[User,CookieAuthenticator] with OAuth2Provider {
override val tokenEndpoint = new DropsTokenEndpoint()
Expand All @@ -38,14 +39,38 @@ class OAuth2Controller @Inject() (
issueAccessToken(oauthDataHandler)
}

def getCode(clientId : String) = SecuredAction.async { implicit request =>
oauthClientDao.find(clientId, None, "authorization_code").flatMap(_ match {
/**
* If a valid client was submitted, a new OAuth code will be generated and send to the clients redirect URI.
*
* Different possibilities to secure webservice communication are supported. First, you can use no security ('none').
* Secondly, you can use a secret ('secret') and last the microservices can be identified using Sluice. Method in use
* will be defined in your application.conf
*
* @author Johann Sell
* @param clientId identifies the client
* @param clientSecret secures the communication, if this method is configured.
* @return
*/
def getCode(clientId : String, clientSecret : String) = SecuredAction.async { implicit request => {

def bodyWithSecret(secret : Option[String]) = oauthClientDao.find(clientId, secret, "authorization_code").flatMap(_ match {
case Some(client) => oauthCodeDao.save(OauthCode(request.identity, client)).map(
code => code.client.redirectUri.map( (uri) => Redirect( uri + code.code)).getOrElse(
code => code.client.redirectUri.map((uri) => Redirect(uri + code.code)).getOrElse(
BadRequest(Messages("oauth2server.clientHasNoRedirectURI"))
)
)
case _ => Future.successful(BadRequest(Messages("oauth2server.clientId.notFound")))
})
}

configuration.getString("drops.ws.security").getOrElse("secret") match {
case "none" => bodyWithSecret(None)
case "secret" if clientSecret != "" => bodyWithSecret(Some(clientSecret))
case "sluice" => {
// TODO: Implement integration for using sluice in intra-microservice communication
Future.successful(BadRequest(Messages("oauth2server.security.method.notImplemented", "sluice")))
}
case _ => Future.successful(BadRequest(Messages("oauth2server.clientSecret.missing")))
}

}}
}
9 changes: 6 additions & 3 deletions app/controllers/Roles.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import models._
import play.api.data.Form
import play.api.data.Forms._
import services.UserService
import utils.WithRole
import utils.authorization.{Pool1Restriction, WithRole}

import scala.concurrent.ExecutionContext.Implicits.global
/**
Expand All @@ -22,13 +22,16 @@ import scala.concurrent.ExecutionContext.Implicits.global
class Roles @Inject() (
userService: UserService,
val messagesApi: MessagesApi,
configuration: Configuration,
val env:Environment[User,CookieAuthenticator]) extends Silhouette[User,CookieAuthenticator] {

def index = SecuredAction(WithRole(RoleAdmin)).async { request =>
val pool1Export = configuration.getBoolean("pool1.export").getOrElse(false)

def index = SecuredAction(WithRole(RoleAdmin) && Pool1Restriction(pool1Export)).async { request =>
userService.list.map(users => Ok(views.html.roles.index(request.identity, request.authenticator.loginInfo, RolesForms.setUsers(users))(request, messagesApi.preferred(request)))) //RolesForms.setUsers(users)
}

def update = SecuredAction(WithRole(RoleAdmin)).async { request =>
def update = SecuredAction(WithRole(RoleAdmin) && Pool1Restriction(pool1Export)).async { request =>
RolesForms.set.bindFromRequest()(request).fold(
bogusForm => Future.successful(BadRequest(
views.html.roles.index(request.identity, request.authenticator.loginInfo, bogusForm)(request, messagesApi.preferred(request))
Expand Down
41 changes: 41 additions & 0 deletions app/utils/authorization/CombinableRestriction.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package utils.authorization

import com.mohiva.play.silhouette.api.Authorization
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
import models.User
import play.api.i18n.Messages
import play.api.mvc.Request
import scala.concurrent.ExecutionContext.Implicits.global

import scala.concurrent.Future

case class AuthAndCombination(one: CombinableRestriction, two: CombinableRestriction) extends Authorization[User,CookieAuthenticator] {
override def isAuthorized[B](identity: User, authenticator: CookieAuthenticator)(implicit request: Request[B], messages: Messages): Future[Boolean] =
one.isAuthorized(identity, authenticator).flatMap(
(first) => two.isAuthorized(identity, authenticator).map(
(second) => first && second
)
)
}


case class AuthOrCombination(one: CombinableRestriction, two: CombinableRestriction) extends Authorization[User,CookieAuthenticator] {
override def isAuthorized[B](identity: User, authenticator: CookieAuthenticator)(implicit request: Request[B], messages: Messages): Future[Boolean] =
one.isAuthorized(identity, authenticator).flatMap(
(first) => two.isAuthorized(identity, authenticator).map(
(second) => first || second
)
)
}

trait CombinableRestriction extends Authorization[User,CookieAuthenticator] {
def isAuthorized[B](identity: User, authenticator: CookieAuthenticator)(implicit request: Request[B], messages: Messages): Future[Boolean]

def &&(other: CombinableRestriction) : Authorization[User,CookieAuthenticator] = {
AuthAndCombination(this, other)
}

def ||(other: CombinableRestriction) : Authorization[User,CookieAuthenticator] = {
AuthOrCombination(this, other)
}
}
25 changes: 25 additions & 0 deletions app/utils/authorization/Pool1Restriction.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package utils.authorization

import com.mohiva.play.silhouette.api.Authorization
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
import models.{Role, RoleAdmin, User}
import play.api.i18n.Messages
import play.api.mvc.Request

import scala.concurrent.Future

/**
* Gets a parameter indicating if an active connection to Pool 1 exists. If the parameter is true (connection exists):
* Only admins have permissions to access the requested resource; all other users are rejected. If it's false, there is
* no need to consider this restriction.
*
* @author Johann Sell
* @param active indicates if a connection does exists
*/
case class Pool1Restriction(active: Boolean) extends Authorization[User,CookieAuthenticator] with CombinableRestriction {
def isAuthorized[B](user: User, authenticator: CookieAuthenticator)(implicit request : Request[B], messages: Messages) =
user.roles match {
case list: Set[Role] => Future.successful(list.contains(RoleAdmin) || !active)
case _ => Future.successful(!active)
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package utils
package utils.authorization

import com.mohiva.play.silhouette.api.Authorization
import com.mohiva.play.silhouette.impl.authenticators.CookieAuthenticator
import models.{Role, User}
import play.api.i18n._
import play.api.mvc.{Request, RequestHeader}
import play.api.mvc.Request

import scala.concurrent.Future

/**
* Check for authorization
*/
case class WithRole(role: Role) extends Authorization[User,CookieAuthenticator] {
case class WithRole(role: Role) extends Authorization[User,CookieAuthenticator] with CombinableRestriction {
def isAuthorized[B](user: User, authenticator: CookieAuthenticator)(implicit request : Request[B], messages: Messages) =
user.roles match {
case list: Set[Role] => Future.successful(list.contains(role))
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import com.typesafe.sbt.packager.docker.Cmd

name := """Drops"""

version := "0.20.14"
version := "0.21.15"

lazy val root = (project in file(".")).enablePlugins(PlayScala).enablePlugins(DockerPlugin)

Expand Down
Loading

0 comments on commit a5cc2ab

Please sign in to comment.