Skip to content

Commit

Permalink
#2 Integration with Lagom Scala DSL
Browse files Browse the repository at this point in the history
  • Loading branch information
gnuzzz committed Oct 31, 2018
1 parent 65d571d commit a188da6
Show file tree
Hide file tree
Showing 13 changed files with 593 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -101,6 +101,7 @@ local.properties
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
*.iml

# CMake
cmake-build-*/
Expand Down
6 changes: 5 additions & 1 deletion findbugs-exclude.xml
@@ -1,2 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter/>
<FindBugsFilter>
<Match>
<Source name="~.*\.scala" />
</Match>
</FindBugsFilter>
38 changes: 38 additions & 0 deletions lagom-pac4j_2.11/pom.xml
Expand Up @@ -25,19 +25,43 @@
<version>${lagom.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-server_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>provided</scope>
</dependency>
<!-- tests -->
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-javadsl-testkit_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-testkit_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-logback_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_${scala.binary.version}</artifactId>
<version>${scalatest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.softwaremill.macwire</groupId>
<artifactId>macros_${scala.binary.version}</artifactId>
<version>${macwire.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -88,6 +112,20 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.scalatest</groupId>
<artifactId>scalatest-maven-plugin</artifactId>
<version>2.0.0</version>
<executions>
<execution>
<id>test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
38 changes: 38 additions & 0 deletions lagom-pac4j_2.12/pom.xml
Expand Up @@ -25,19 +25,43 @@
<version>${lagom.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-server_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>provided</scope>
</dependency>
<!-- tests -->
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-javadsl-testkit_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-scaladsl-testkit_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.lightbend.lagom</groupId>
<artifactId>lagom-logback_${scala.binary.version}</artifactId>
<version>${lagom.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_${scala.binary.version}</artifactId>
<version>${scalatest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.softwaremill.macwire</groupId>
<artifactId>macros_${scala.binary.version}</artifactId>
<version>${macwire.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -88,6 +112,20 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.scalatest</groupId>
<artifactId>scalatest-maven-plugin</artifactId>
<version>2.0.0</version>
<executions>
<execution>
<id>test</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
3 changes: 3 additions & 0 deletions pom.xml
Expand Up @@ -44,6 +44,8 @@
<junit.jupiter.version>5.3.1</junit.jupiter.version>
<assertj.version>3.11.1</assertj.version>
<jackson.version>2.9.4</jackson.version>
<scalatest.version>3.0.1</scalatest.version>
<macwire.version>2.3.1</macwire.version>
</properties>

<modules>
Expand Down Expand Up @@ -153,6 +155,7 @@
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
Expand Down
@@ -0,0 +1,62 @@
package org.pac4j.lagom.scaladsl

import java.util

import com.lightbend.lagom.scaladsl.api.transport.RequestHeader
import org.pac4j.core.context.session.SessionStore
import org.pac4j.core.context.{Cookie, WebContext}
import org.pac4j.core.exception.TechnicalException

/**
* <p>Implementation web context of PAC4J for Lagom framework.</p>
* <p>Context is immutable and the {@link SessionStore} is not supported.</p>
*
* @author Vladimir Kornyshev
* @since 1.0.0
*/
class LagomWebContext(requestHeader: RequestHeader) extends WebContext {

override def getSessionStore: SessionStore[_ <: WebContext] = throw new TechnicalException("Operation not supported")

override def getRequestParameter(s: String): String = throw new TechnicalException("Operation not supported")

override def getRequestParameters: util.Map[String, Array[String]] = throw new TechnicalException("Operation not supported")

override def getRequestAttribute(s: String): AnyRef = throw new TechnicalException("Operation not supported")

override def setRequestAttribute(s: String, o: Any): Unit = throw new TechnicalException("Operation not supported")

override def getRequestHeader(name: String): String = requestHeader.getHeader(name) match {
case Some(value) => value
case None => throw new TechnicalException(s"Header $name not found")
}

override def getRequestMethod: String = requestHeader.method.name

override def getRemoteAddr: String = throw new TechnicalException("Operation not supported")

override def writeResponseContent(s: String): Unit = throw new TechnicalException("Operation not supported")

override def setResponseStatus(i: Int): Unit = throw new TechnicalException("Operation not supported")

override def setResponseHeader(s: String, s1: String): Unit = throw new TechnicalException("Operation not supported")

override def setResponseContentType(s: String): Unit = throw new TechnicalException("Operation not supported")

override def getServerName: String = throw new TechnicalException("Operation not supported")

override def getServerPort: Int = throw new TechnicalException("Operation not supported")

override def getScheme: String = throw new TechnicalException("Operation not supported")

override def isSecure: Boolean = false

override def getFullRequestURL: String = throw new TechnicalException("Operation not supported")

override def getRequestCookies: util.Collection[Cookie] = throw new TechnicalException("Operation not supported")

override def addResponseCookie(cookie: Cookie): Unit = throw new TechnicalException("Operation not supported")

override def getPath: String = requestHeader.uri.getPath

}
137 changes: 137 additions & 0 deletions shared/src/main/scala/org/pac4j/lagom/scaladsl/SecuredService.scala
@@ -0,0 +1,137 @@
package org.pac4j.lagom.scaladsl

import java.util.Collections.singletonList

import com.lightbend.lagom.scaladsl.api.transport.Forbidden
import com.lightbend.lagom.scaladsl.server.ServerServiceCall
import org.pac4j.core.authorization.authorizer.Authorizer
import org.pac4j.core.client.Client
import org.pac4j.core.config.Config
import org.pac4j.core.credentials.Credentials
import org.pac4j.core.profile.{AnonymousProfile, CommonProfile}

/**
* <p>
* Interface, that implement cross-cutting security concerns for Lagom services.
* </p>
* <p>
* More information about service call composition in Lagom <a href="https://www.lagomframework.com/documentation/current/scala/ServiceImplementation.html#Service-call-composition">documentation</a>.
* </p>
*
* @author Vladimir Kornyshev
* @since 1.0.0
*/
trait SecuredService {

/**
* Get configuration of pac4j for this service.
*
* @return pac4j configuration
*/
def securityConfig: Config

/**
* Service call composition for authentication.
*
* @param serviceCall Service call
* @tparam Request Type of request
* @tparam Response Type of response
* @return Service call with authentication logic
*/
def authenticate[Request, Response](
serviceCall: CommonProfile => ServerServiceCall[Request, Response]): ServerServiceCall[Request, Response] =
authenticate(securityConfig.getClients.getDefaultSecurityClients, serviceCall)

/**
* Service call composition for authentication.
*
* @param clientName Name of authentication client
* @param serviceCall Service call
* @tparam Request Type of request
* @tparam Response Type of response
* @return Service call with authentication logic
*/
def authenticate[Request, Response](
clientName: String, serviceCall: CommonProfile => ServerServiceCall[Request, Response]): ServerServiceCall[Request, Response] =
ServerServiceCall.compose { requestHeader =>
val profile = try {
val clients = securityConfig.getClients
val defaultClient = clients.findClient(clientName).asInstanceOf[Client[Credentials, CommonProfile]]
val context = new LagomWebContext(requestHeader)
val credentials = defaultClient.getCredentials(context)
defaultClient.getUserProfile(credentials, context)
} catch {
case ex: Exception =>
// We can throw only TransportException.
// Otherwise exception will be sent to the client with stack trace.
new AnonymousProfile
}

serviceCall.apply(Option(profile).getOrElse(new AnonymousProfile))
}

/**
* Service call composition for authorization.
*
* @param authorizer Authorizer (may be composite)
* @param serviceCall Service call
* @tparam Request Type of request
* @tparam Response Type of response
* @return Service call with authorization logic
*/
def authorize[Request, Response](
authorizer: Authorizer[CommonProfile], serviceCall: CommonProfile => ServerServiceCall[Request, Response]): ServerServiceCall[Request, Response] =
authorize(securityConfig.getClients.getDefaultSecurityClients, authorizer, serviceCall)

/**
* Service call composition for authorization.
*
* @param clientName Name of authentication client
* @param authorizer Authorizer (may be composite)
* @param serviceCall Service call
* @tparam Request Type of request
* @tparam Response Type of response
* @return Service call with authorization logic
*/
def authorize[Request, Response](
clientName: String, authorizer: Authorizer[CommonProfile], serviceCall: CommonProfile => ServerServiceCall[Request, Response]): ServerServiceCall[Request, Response] =
authenticate(clientName, (profile: CommonProfile) => ServerServiceCall.compose { requestHeader =>
val authorized = try {
authorizer != null && authorizer.isAuthorized(new LagomWebContext(requestHeader), singletonList(profile))
} catch {
case ex: Exception =>
// We can throw only TransportException.
// Otherwise exception will be sent to the client with stack trace.
false
}
if (!authorized) throw Forbidden("Authorization failed")
serviceCall.apply(profile)
})

/**
* Service call composition for authorization.
*
* @param authorizerName Name of authorizer, registered in security config
* @param serviceCall Service call
* @tparam Request Type of request
* @tparam Response Type of response
* @return Service call with authorization logic
*/
def authorize[Request, Response](
authorizerName: String, serviceCall: CommonProfile => ServerServiceCall[Request, Response]): ServerServiceCall[Request, Response] =
authorize(securityConfig.getAuthorizers.get(authorizerName).asInstanceOf[Authorizer[CommonProfile]], serviceCall)

/**
* Service call composition for authorization.
*
* @param clientName Name of authentication client
* @param authorizerName Name of authorizer, registered in security config
* @param serviceCall Service call
* @tparam Request Type of request
* @tparam Response Type of response
* @return Service call with authorization logic
*/
def authorize[Request, Response](
clientName: String, authorizerName: String, serviceCall: CommonProfile => ServerServiceCall[Request, Response]): ServerServiceCall[Request, Response] =
authorize(clientName, securityConfig.getAuthorizers.get(authorizerName).asInstanceOf[Authorizer[CommonProfile]], serviceCall)
}
14 changes: 14 additions & 0 deletions shared/src/test/scala/org/pac4j/lagom/scaladsl/ClientNames.scala
@@ -0,0 +1,14 @@
package org.pac4j.lagom.scaladsl

/**
* Names of clients, that will be used for tests.
*
* @author Vladimir Kornyshev
* @since 1.0.0
*/
object ClientNames {

val HEADER_CLIENT = "simple_header"
val HEADER_JWT_CLIENT = "jwt_header"

}

0 comments on commit a188da6

Please sign in to comment.