Skip to content

Commit

Permalink
#3 ports code from AtlasOfLivingAustralia/ala-cas-2.0, updates JNDI f…
Browse files Browse the repository at this point in the history
…or running in boot repackage configuration
  • Loading branch information
sbearcsiro committed Aug 9, 2017
1 parent 13f006a commit c7b0e2b
Show file tree
Hide file tree
Showing 21 changed files with 591 additions and 76 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@ By default, CAS will load properties from `/etc/cas/config` but can be changed i
- `cas.ticket.registry.jpa` should have `url`, `user` and `password` set for the ticket database. For the first run
of each version `ddl-auto` should be set to `update` to get the latest changes from JPA.

*NOTE:* The MySQL driver used by this CAS version is v6.0.6. This DB driver requires that the server returns a timezone
*NOTE:* The MySQL driver used by this CAS version is 8.0.7. This DB driver requires that the server returns a timezone
in the form `Australia/Sydney`, whereas the MySQL versions available on Ubuntu 16.04 will tell the client `AEST` (which
causes an Exception in the client). The simplest fix is to override the server timezone in the JDBC URL by appending
`?serverTimezone=Australia/Sydney` to the URL.

*IF USING MYSQL Connector 6.0+* note that the `UserCreatorALA` uses Spring JDBC's `SimpleJdbcCall` to execute a stored procedure.
Spring JDBC makes some assumptions about the behaviour of the JDBC driver which [changed in `mysql-connector-java` 6.0+]()
and as such the following driver properties are also required:
- `nullCatalogMeansCurrent=true`
- `nullNamePatternMatchesAll=true`

## Legacy passwords

CAS no longer provides an MD5 type password encoder. To that end a custom password encoder is used by the system,
Expand Down Expand Up @@ -70,8 +76,8 @@ the default ALA CAS configuration a datasource is used to share the same connect
- monitoring.

For ease of configuration, ALA CAS uses Simple JNDI to create a writable JNDI sub-directory. Hikari Connection Pool
datasource(s) are then inserted into JNDI directory. See `au.org.ala.cas.ServletContextConfig` for the JNDI insertion and
`au.org.ala.cas.JndiConfigurationProperties` for the Spring Boot configuration types.
datasource(s) are then inserted into JNDI directory. See `au.org.ala.cas.jndi.ServletContextConfig` for the JNDI insertion and
`au.org.ala.cas.jndi.JndiConfigurationProperties` for the Spring Boot configuration types.

Instead of using the Simple JNDI based datasource, a container based source could be used instead. Use
`dataSourceName: java:comp/env/<name>` for each JDBC property and remove `jndi.hikari` in `application.yml`.
Expand Down
8 changes: 3 additions & 5 deletions etc/cas/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ cas:
jdbc:
- dataSourceName: jdbccas
pac4j:
typedIdUsed: false
# autoRedirect: false
# name:
facebook:
# fields:
id: xxxxxxxxxxxxxxx
secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
scope: public_profile,email
Expand All @@ -56,4 +52,6 @@ cas:
url: jdbc:mysql://localhost/casTicketRegistry[?serverTimezone=Australia/Sydney]
user: username
password: password

ala:
userCreator:
userCreatePassword: random-default-password-here
51 changes: 31 additions & 20 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd ">

<!--<parent>-->
<!--<groupId>au.org.ala</groupId>-->
<!--<artifactId>ala-parent-pom</artifactId>-->
<!--<version>2</version>-->
<!--</parent>-->
<parent>
<groupId>au.org.ala</groupId>
<artifactId>ala-parent-pom</artifactId>
<version>4</version>
</parent>

<modelVersion>4.0.0</modelVersion>
<groupId>au.org.ala</groupId>
<!--<groupId>au.org.ala</groupId>-->
<artifactId>cas</artifactId>
<packaging>war</packaging>
<!-- Version should ${cas.version}-${ala.build.version}(-SNAPSHOT)? -->
Expand All @@ -29,9 +29,17 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${springboot.version}</version>
<executions>
<execution>
<goals><goal>repackage</goal></goals>
</execution>
</executions>
<configuration>
<mainClass>org.springframework.boot.loader.WarLauncher</mainClass>
<mainClass>org.apereo.cas.web.CasWebApplication</mainClass>
<addResources>true</addResources>
<classifier>boot</classifier>
<executable>true</executable>
<!--<skip>false</skip>-->
</configuration>
</plugin>
<plugin>
Expand Down Expand Up @@ -95,6 +103,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.2</version>
<executions>
<!-- Replacing default-compile as it is treated specially by maven -->
<execution>
Expand Down Expand Up @@ -158,17 +167,7 @@
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.h-thurow</groupId>
<artifactId>simple-jndi</artifactId>
<version>0.14.0</version>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
Expand All @@ -181,6 +180,12 @@
<version>${springboot.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>${springboot.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-webapp${app.server}</artifactId>
Expand Down Expand Up @@ -216,7 +221,7 @@
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-pac4j-webflow</artifactId>
<version>5.1.3-SNAPSHOT</version>
<version>${cas.version}</version>
</dependency>
<!-- BELOW IS STUFF PROVIDED BY THE CAS WAR BUT LISTED HERE FOR
TO ALLOW THE IDE TO FIND IT -->
Expand Down Expand Up @@ -317,6 +322,7 @@
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-security-filter</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
Expand Down Expand Up @@ -381,7 +387,7 @@
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
<version>5.1.43</version>
<scope>runtime</scope>
</dependency>

Expand All @@ -390,6 +396,11 @@
<!--<artifactId>datasource-proxy</artifactId>-->
<!--<version>1.4.1</version>-->
<!--</dependency>-->
<dependency>
<groupId>com.github.h-thurow</groupId>
<artifactId>simple-jndi</artifactId>
<version>0.15.0</version>
</dependency>
</dependencies>

<properties>
Expand Down
24 changes: 24 additions & 0 deletions src/main/kotlin/au/org/ala/cas/AlaCasProperties.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package au.org.ala.cas

import org.apereo.cas.configuration.model.core.authentication.PasswordEncoderProperties
import org.apereo.cas.configuration.model.support.cookie.CookieProperties
import org.apereo.cas.configuration.model.support.jpa.AbstractJpaProperties
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(value = "ala")
open class AlaCasProperties(
var cookie: CookieProperties = CookieProperties(),
var userCreator: UserCreatorProperties = UserCreatorProperties()
)

open class UserCreatorProperties(
var passwordEncoder: PasswordEncoderProperties = PasswordEncoderProperties(),
var jdbc: JDBCUserCreatorProperties = UserCreatorProperties.JDBCUserCreatorProperties(),
var userCreatePassword: String = ""
) {
open class JDBCUserCreatorProperties : AbstractJpaProperties() {
var createUserProcedure: String = "sp_create_user"
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package au.org.ala.cas.delegated;

import au.org.ala.cas.delegated.UserCreator
import au.org.ala.cas.delegated.UserCreatorALA
import au.org.ala.cas.AlaCasProperties
import au.org.ala.utils.logger
import org.apereo.cas.authentication.principal.PrincipalFactory
import org.apereo.cas.authentication.principal.PrincipalResolver
import org.apereo.cas.configuration.support.Beans
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
@EnableConfigurationProperties(AlaCasProperties::class)
open class AlaPac4jAuthenticationConfiguration {

companion object {
private val logger = logger<AlaPac4jAuthenticationConfiguration>()
}

@Autowired
lateinit var alaCasProperties: AlaCasProperties

@Bean
open fun userCreator(): UserCreator = UserCreatorALA().apply {
logger.debug("Creating UserCreatorALA with {}, {}, {}, {}", alaCasProperties.userCreator.jdbc.dataSourceName, alaCasProperties.userCreator.userCreatePassword, alaCasProperties.userCreator.jdbc.createUserProcedure)
dataSource = Beans.newDataSource(alaCasProperties.userCreator.jdbc)
passwordEncoder = Beans.newPasswordEncoder(alaCasProperties.userCreator.passwordEncoder)
userCreatePassword = alaCasProperties.userCreator.userCreatePassword
createUserProcedure = alaCasProperties.userCreator.jdbc.createUserProcedure
}

@Bean(name = arrayOf("clientPrincipalFactory"))
open fun clientPrincipalFactory(
@Autowired personDirectoryPrincipalResolver: PrincipalResolver,
@Autowired userCreator: UserCreator
): PrincipalFactory = AlaPrincipalFactory(personDirectoryPrincipalResolver, userCreator)
}
88 changes: 88 additions & 0 deletions src/main/kotlin/au/org/ala/cas/delegated/AlaPrincipalFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package au.org.ala.cas.delegated

import au.org.ala.cas.delegated.UserCreator
import au.org.ala.utils.logger
import org.apache.commons.lang3.builder.HashCodeBuilder
import org.apereo.cas.authentication.Credential
import org.apereo.cas.authentication.principal.Principal
import org.apereo.cas.authentication.principal.PrincipalFactory
import org.apereo.cas.authentication.principal.PrincipalResolver
import javax.security.auth.login.FailedLoginException

class AlaPrincipalFactory(private val principalResolver: PrincipalResolver,
val userCreator: UserCreator): PrincipalFactory {

companion object {
private const val serialVersionUID: Long = -3999695695604948495L
private val logger = logger<AlaPrincipalFactory>()
val EMAIL_PATTERN = Regex("^.+@.+\\..+$")
}

override fun createPrincipal(id: String) = createAlaPrincipal(id, emptyMap())

override fun createPrincipal(id: String, attributes: Map<String, Any>) = createAlaPrincipal(id, attributes)

private fun createAlaPrincipal(id: String, attributes: Map<String, Any>): Principal {
val attributeParser = AttributeParser.create(id, attributes)
val email = attributeParser.findEmail()
logger.debug("email : {}", email)

if (email == null || !EMAIL_PATTERN.matches(email)) {
logger.debug("Invalid email : {}, authentication aborted!", email)
throw FailedLoginException("No email address found in $email; email address is required to lookup (and/or create) ALA user!")
}

//NOTE: just in case social media gave us email containing Upper case letters
val emailAddress = email.toLowerCase()
val alaCredential = Credential() { -> emailAddress } // SAM conversion

// get the ALA user attributes from the userdetails DB ("userid", "firstname", "lastname", "authority")
var principal = principalResolver.resolve(alaCredential)
logger.debug("{} resolved principal: {}", principalResolver, principal)

// does the ALA user exist?
if (!validatePrincipalALA(principal)) {
// create a new ALA user in the userdetails DB
logger.debug("user {} not found in ALA userdetails DB, creating new ALA user for: {}.", emailAddress, emailAddress)

val firstName = attributeParser.findFirstname()
val lastName = attributeParser.findLastname()

if (firstName != null && lastName != null) {
// if no userId parameter is returned then no db entry was created
val userId = userCreator.createUser(emailAddress, firstName, lastName) ?: throw FailedLoginException("Unable to create user for $emailAddress, $firstName, $lastName")
logger.debug("Received new user id $userId")

// re-try (we have to retry, because that is how we get the required "userid")
principal = principalResolver.resolve(alaCredential)

logger.debug("{} resolved principal: {}", principalResolver, principal)
} else {
logger.warn("Couldn't extract firstname or lastname for {} from attributes", id, attributes)
}

if (!validatePrincipalALA(principal)) {
// we failed to lookup ALA user (most likely because the creation above failed), complain, throw exception, etc.
throw FailedLoginException("Unable to create ALA user for $emailAddress with attributes $attributes")
}
}
return principal
}

internal fun validatePrincipalALA(principal: Principal?) = principal != null && principal.attributes != null && principal.attributes.containsKey("userid")


override fun equals(other: Any?): Boolean {
if (other == null) {
return false
}
if (other === this) {
return true
}
return other.javaClass == javaClass
}

override fun hashCode(): Int {
return HashCodeBuilder(13, 37).toHashCode()
}
}
Loading

0 comments on commit c7b0e2b

Please sign in to comment.