A Play2 module implementing a small extension to the silhouette authorization library. Using the modules developers are able to connect to an OAuth2 provider that has been implemented to fit the needs of Viva con Agua.
Implements an OAuth2 client for Play2 apps. It allows to use Drops as social provider and tailors the OAuth2 handshake to specific organizational support. First, a user has to be logged out if and only if, a user has been logged out from Drops. Thus play2-oauth-client implements an additional Object-Event-System (OES) using the nats message broker.
Resolve the library from Sonatype in your build.sbt
:
resolvers ++= Seq(
Resolver.sonatypeRepo("public"),
Resolver.bintrayRepo("scalaz", "releases")
)
libraryDependencies += "org.vivaconagua" %% "play2-oauth-client" % "0.4.7-play27"
// it's important to handle this by your application while play2-oauth-client is using scala_nats
libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.8.1"
Resolve the library from Sonatype in your build.sbt
:
resolvers += "Sonatype OSS Releases" at "https://oss.sonatype.org/service/local/staging/deploy/maven2"
libraryDependencies += "org.vivaconagua" %% "play2-oauth-client" % "0.4.6-play25"
You have to use the lib in a controller. You can simply implement the DropsLoginController
trait:
package controllers
import javax.inject.Inject
import com.mohiva.play.silhouette.api.Silhouette
import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
import com.mohiva.play.silhouette.impl.providers.SocialProviderRegistry
import org.vivaconagua.play2OauthClient.silhouette.CookieEnv
import org.vivaconagua.play2OauthClient.controller.DropsLoginController
import org.vivaconagua.play2OauthClient.silhouette.UserService
import concurrent.ExecutionContext.Implicits.global
import play.api.mvc._
import play.api.libs.ws._
import play.api._
import play.api.cache.CacheApi
class DropsController @Inject()(
ws: WSClient,
override val conf : Configuration,
cc: ControllerComponents,
override val silhouette: Silhouette[CookieEnv],
override val userService: UserService,
override val authInfoRepository: AuthInfoRepository,
override val socialProviderRegistry: SocialProviderRegistry,
cache: CacheApi
) extends AbstractController(cc) with DropsLoginController {
override val defaultRedirectUrl = routes.HomeController.index.url // defines the default page a user sees after login
}
Now you have to add the following routes to your conf/routes
file. These routes are needed to redirect the users
client in order to fulfill the OAuth2 handshake with the social provider Drops:
GET /authenticate/:provider controllers.DropsController.authenticate(provider, route: Option[String], ajax: Option[Boolean])
POST /authenticate/:provider controllers.DropsController.authenticate(provider, route: Option[String], ajax: Option[Boolean])
Furthermore, if you implement a JavaScript WebApp, you can add the following route to your routes
file:
GET /identity controllers.DropsController.frontendLogin
If your user has a valid session with Drops, you will receive:
{
"uuid": "<your-users-uuid>"
}
Otherwise, you will receive a JSON encoded error message using the following format:
{
"http_error_code": 401,
"internal_error_code": "401.OAuth2Server",
"msg": "Currently, there is no authenticated user.",
"msg_i18n": "error.oauth2.not.authenticated",
"additional_information": {
"oauth2_client": "<a microservice identifier>"
}
}
Since play2-oauth-client has to communicate with your nats message broker and Drops
you have to add the following lines to your conf/application.conf
:
nats.endpoint="nats://<nats_ip>:<nats_port>" // default port is 4222
ms.name="<your_ms_name>" // example: BLOOB
ms.host="<your_ms_domain>" // example: http://localhost:9000
ms.entrypoint="<your_ms_route>" // the route that you have configured before, example: /authenticate/drops
drops.url.base="<drops_domain>" // example: http://localhost:9100 or https://pool.vivaconagua.org/drops
drops.client_id="<your_ms_id>" // the id that has been configured in drops to identify your microservice
drops.client_secret="<your_ms_secret>" // the secret that has been configured in drops to identify your microservice
play.filters.enabled += org.vivaconagua.play2OauthClient.drops.AuthOESFilter
An example controller using the implemented authentification:
import javax.inject.Inject
import play.api.mvc._
import com.mohiva.play.silhouette.api.Silhouette
import org.vivaconagua.play2OauthClient.silhouette.CookieEnv
import org.vivaconagua.play2OauthClient.silhouette.UserService
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import play.api.mvc.AnyContent
import play.api.Logger
/**
* A very small controller that renders a home page.
*/
class HomeController @Inject()(
cc: ControllerComponents,
silhouette: Silhouette[CookieEnv],
userService: UserService
) extends AbstractController(cc) {
val logger: Logger = Logger(this.getClass())
def userTest = silhouette.SecuredAction.async { implicit request => {
Future.successful(Ok("User: " + request.identity))
}}
}
Currently, a role-based access control (RbAC) is implemented for the Pool². Using it requires basic knowledge about the designed roles:
- Supporter: Everyone is a supporter.
- Employee: Everyone who is employee of Viva con Agua (e.V, Wasser, Goldeimer, Stiftung or Arts create Water).
- Admin: Group with controling rights for the technical system Pool².
- Volunteer Manager: All the supporter volunteering as managers of their crew. Thus, the group of users can be
separated by their crews, but also by their area of responsability (so called
pillar
). There are four areas of responsibility:finance
,operation
,education
andnetwork
.
The package org.vivaconagua.play2OauthClient.drops.authorization
allows you to use RbAC to secure your endpoints:
def getAll = silhouette.SecuredAction(
(IsVolunteerManager() && IsResponsibleFor("finance")) || IsEmployee || IsAdmin
).async { request =>
...
}
Only volunteer manager with the responsability for finance
, employees and admins have access rights for
getAll
. Additionally, you can pass the crews UUID or name to the VolunteerManager()
instance. Thus, an action will
be accessable only for employees, admins and Volunteer Managers of the given crew.
Todo