Skip to content

Commit

Permalink
Merge branch 'security'
Browse files Browse the repository at this point in the history
  • Loading branch information
Dierk Koenig committed Nov 21, 2023
2 parents 4792448 + cca228b commit 4bd1ad2
Show file tree
Hide file tree
Showing 14 changed files with 535 additions and 6 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ dependencies {
implementation "org.grails.plugins:async"
implementation "org.grails.plugins:scaffolding"
implementation "org.grails.plugins:hibernate5"

// see https://mvnrepository.com/artifact/org.grails.plugins/spring-security-core
implementation 'org.grails.plugins:spring-security-core:5.3.0' // added to include security

implementation "org.hibernate:hibernate-core:5.6.11.Final"
implementation "org.grails.plugins:events"
implementation "org.grails.plugins:gsp"
Expand Down
52 changes: 52 additions & 0 deletions grails-app/conf/application.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@


// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'rooms.SecUser'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'rooms.SecUserSecRole'
grails.plugin.springsecurity.authority.className = 'rooms.SecRole'

grails.plugin.springsecurity.logout.postOnly = false // allow logout via get url /logout

grails.plugin.springsecurity.controllerAnnotations.staticRules = [
[pattern: '/', access: ['permitAll']],
[pattern: '/error', access: ['permitAll']],
[pattern: '/index', access: ['permitAll']],
[pattern: '/index.gsp', access: ['permitAll']],
[pattern: '/shutdown', access: ['permitAll']],
[pattern: '/assets/**', access: ['permitAll']],
[pattern: '/**/js/**', access: ['permitAll']],
[pattern: '/**/css/**', access: ['permitAll']],
[pattern: '/**/images/**', access: ['permitAll']],
[pattern: '/**/favicon.ico', access: ['permitAll']]
]

grails.plugin.springsecurity.filterChain.chainMap = [
[pattern: '/assets/**', filters: 'none'],
[pattern: '/**/js/**', filters: 'none'],
[pattern: '/**/css/**', filters: 'none'],
[pattern: '/**/images/**', filters: 'none'],
[pattern: '/**/favicon.ico', filters: 'none'],
[pattern: '/**', filters: 'JOINED_FILTERS']
]


// config types are 'Annotation', 'Requestmap', or 'InterceptUrlMap'
grails.plugin.springsecurity.securityConfigType = 'InterceptUrlMap'
grails.plugin.springsecurity.interceptUrlMap = [
[pattern: '/static/**', access: ['permitAll']], // static resources are unsecured (HomeSecondSpec running)
[pattern: '/', access: ['permitAll']],
[pattern: '/error', access: ['permitAll']],
[pattern: '/index', access: ['permitAll']],
[pattern: '/index.gsp', access: ['permitAll']],
[pattern: '/shutdown', access: ['permitAll']],
[pattern: '/assets/**', access: ['permitAll']],
[pattern: '/**/js/**', access: ['permitAll']],
[pattern: '/**/css/**', access: ['permitAll']],
[pattern: '/**/images/**', access: ['permitAll']],
[pattern: '/**/favicon.ico', access: ['permitAll']],
[pattern: "/login/auth", access: ["permitAll"]],
[pattern: "/person/**" , access: ['ROLE_ADMIN']], // cannot use constant here :-(
[pattern: "/room/**" , access: ['ROLE_ADMIN']],
[pattern: "/**" , access: ['ROLE_ADMIN', 'ROLE_GUEST']],
]

2 changes: 2 additions & 0 deletions grails-app/conf/spring/resources.groovy
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import rooms.SecUserPasswordEncoderListener
// Place your Spring DSL code here
beans = {
secUserPasswordEncoderListener(SecUserPasswordEncoderListener)
}
26 changes: 26 additions & 0 deletions grails-app/domain/rooms/SecRole.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package rooms

import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import grails.compiler.GrailsCompileStatic

@GrailsCompileStatic
@EqualsAndHashCode(includes='authority')
@ToString(includes='authority', includeNames=true, includePackage=false)
class SecRole implements Serializable {

private static final long serialVersionUID = 1

static final String ADMIN = "ROLE_ADMIN"
static final String GUEST = "ROLE_GUEST"

String authority

static constraints = {
authority nullable: false, blank: false, unique: true
}

static mapping = {
cache true
}
}
33 changes: 33 additions & 0 deletions grails-app/domain/rooms/SecUser.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package rooms

import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString
import grails.compiler.GrailsCompileStatic

@GrailsCompileStatic
@EqualsAndHashCode(includes='username')
@ToString(includes='username', includeNames=true, includePackage=false)
class SecUser implements Serializable {

private static final long serialVersionUID = 1

String username
String password
boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired

Set<SecRole> getAuthorities() {
(SecUserSecRole.findAllBySecUser(this) as List<SecUserSecRole>)*.secRole as Set<SecRole>
}

static constraints = {
password nullable: false, blank: false, password: true
username nullable: false, blank: false, unique: true
}

static mapping = {
password column: '`password`'
}
}
87 changes: 87 additions & 0 deletions grails-app/domain/rooms/SecUserSecRole.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package rooms

import grails.gorm.DetachedCriteria
import groovy.transform.ToString

import org.codehaus.groovy.util.HashCodeHelper
import grails.compiler.GrailsCompileStatic

@GrailsCompileStatic
@ToString(cache=true, includeNames=true, includePackage=false)
class SecUserSecRole implements Serializable {

private static final long serialVersionUID = 1

SecUser secUser
SecRole secRole

@Override
boolean equals(other) {
if (other instanceof SecUserSecRole) {
other.secUserId == secUser?.id && other.secRoleId == secRole?.id
}
}

@Override
int hashCode() {
int hashCode = HashCodeHelper.initHash()
if (secUser) {
hashCode = HashCodeHelper.updateHash(hashCode, secUser.id)
}
if (secRole) {
hashCode = HashCodeHelper.updateHash(hashCode, secRole.id)
}
hashCode
}

static SecUserSecRole get(long secUserId, long secRoleId) {
criteriaFor(secUserId, secRoleId).get()
}

static boolean exists(long secUserId, long secRoleId) {
criteriaFor(secUserId, secRoleId).count()
}

private static DetachedCriteria criteriaFor(long secUserId, long secRoleId) {
SecUserSecRole.where {
secUser == SecUser.load(secUserId) &&
secRole == SecRole.load(secRoleId)
}
}

static SecUserSecRole create(SecUser secUser, SecRole secRole, boolean flush = false) {
def instance = SecUserSecRole.findOrCreateWhere(secUser: secUser, secRole: secRole)
instance.save(flush: flush)
instance
}

static boolean remove(SecUser u, SecRole r) {
if (u != null && r != null) {
SecUserSecRole.where { secUser == u && secRole == r }.deleteAll()
}
}

static int removeAll(SecUser u) {
u == null ? 0 : SecUserSecRole.where { secUser == u }.deleteAll() as int
}

static int removeAll(SecRole r) {
r == null ? 0 : SecUserSecRole.where { secRole == r }.deleteAll() as int
}

static constraints = {
secUser nullable: false
secRole nullable: false, validator: { SecRole r, SecUserSecRole ur ->
if (ur.secUser?.id) {
if (SecUserSecRole.exists(ur.secUser.id, r.id)) {
return ['userRole.exists']
}
}
}
}

static mapping = {
id composite: ['secUser', 'secRole']
version false
}
}
27 changes: 22 additions & 5 deletions grails-app/init/rooms/BootStrap.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,33 @@ class BootStrap {
*/
def init = { servletContext ->

if (Environment.current == Environment.PRODUCTION) { return; } // guard clause
if (Environment.current == Environment.PRODUCTION) { return } // guard clause

// in production or test, this might already be in the DB
SecRole adminRole = save(SecRole.findOrCreateWhere(authority: SecRole.ADMIN))
SecRole guestRole = save(SecRole.findOrCreateWhere(authority: SecRole.GUEST))

SecUser testUser = save(new SecUser(username: 'me', password: 'bad'))
SecUser guest = save(new SecUser(username: 'guest', password: 'guest'))

testUser.withTransaction { tx ->
SecUserSecRole.create(testUser, adminRole, true) //flush
SecUserSecRole.create(guest, guestRole, true)
}

// plausibility check
assert SecRole.count() == 2
assert SecUser.count() == 2
assert SecUserSecRole.count() == 2

// in dev and test mode, we populate the database with some sample data to work with

Person dierk = save(new Person(firstName: "Dierk", lastName: "König"))
Person dieter = save(new Person(firstName: "Dieter", lastName: "Holz"))

Room room_5_1C54 = save(new Room(name: "5.1C54", capacity: 40));
Room room_5_2H51 = save(new Room(name: "5.2H51", capacity: 40));
Room room_1_013 = save(new Room(name: "1.013" , capacity: 80));
Room room_5_1C54 = save(new Room(name: "5.1C54", capacity: 40))
Room room_5_2H51 = save(new Room(name: "5.2H51", capacity: 40))
Room room_1_013 = save(new Room(name: "1.013" , capacity: 80))

Date mspDate = new SimpleDateFormat("yyyy-MM-dd").parse("2024-02-06")

Expand Down Expand Up @@ -57,7 +74,7 @@ class BootStrap {
* @param domainObject
* @return Object - the saved domain object
*/
static save(domainObject) {
static <T> T save(T domainObject) {
domainObject.save(failOnError: true) // will throw an exception if validation fails
return domainObject
}
Expand Down
12 changes: 12 additions & 0 deletions grails-app/views/layouts/main.gsp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@
</div>
</nav>

<div style="margin-left:2rem;">
<h2>
<sec:ifLoggedIn>
user: <sec:username/>
<g:link controller="logout">log out</g:link>
</sec:ifLoggedIn>
<sec:ifNotLoggedIn>
<g:link controller="login">log in</g:link>
</sec:ifNotLoggedIn>
</h2>
</div>

<g:layoutBody/>

<div class="footer" role="contentinfo">
Expand Down
109 changes: 109 additions & 0 deletions grails-app/views/login/auth.gsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main"/>
<title>
Anmelden
</title>

<style media="screen">
#login {
margin: 15px 0;
padding: 0;
text-align: center;
}
#login .inner {
width: 340px;
padding-bottom: 6px;
margin: 60px auto;
text-align: left;
border: 1px solid #aab;
background-color: #d9d9e3;
box-shadow: 2px 2px 2px #eee;
}
#login .inner .fheader {
padding: 18px 26px 14px 26px;
background-color: #f7f7ff;
margin: 0 0 14px 0;
color: #2e3741;
font-size: 18px;
font-weight: bold;
}
#login .inner .cssform p {
clear: left;
padding: 4px 0 3px 105px;
margin: 0 0 20px;
height: 1%;
}
#login .inner .cssform input[type="text"] {
width: 120px;
}
#login .inner .cssform label {
font-weight: bold;
float: left;
text-align: right;
margin-left: -105px;
width: 110px;
padding-top: 3px;
padding-right: 10px;
padding-left: 1em;
}
#login #remember_me_holder {
padding-left: 114px;
}
#login #submit {
margin-left: 15px;
}
#login #remember_me_holder label {
float: right;
margin-left: 0;
text-align: left;
width: 200px;
padding-left: 4px;
padding-top: 0;
}
#login .inner .login_message {
padding: 6px 25px 20px 25px;
color: #c33;
}
#login .inner .text_ {
width: 120px;
}
#login .inner .chk {
/*height: 12px;*/
}
</style>
</head>

<body>

<div id="login">
<div class="inner">
<div class="fheader">Bitte anmelden</div>


<form action="/login/authenticate" method="POST" id="loginForm" class="cssform" autocomplete="off">
<p>
<label for="username">Benutzer</label>
<input type="text" class="text_" name="username" id="username" placeholder="guest"/>
</p>

<p>
<label for="password">Passwort</label>
<input type="password" class="text_" name="password" id="password" placeholder="guest"/>
</p>

<p id="remember_me_holder">
<input type="checkbox" class="chk" name="remember-me" id="remember_me"/>
<label for="remember_me">Angemeldet bleiben</label>
</p>

<p>
<input type="submit" id="submit" value="Anmelden"/>
</p>
</form>
</div>
</div>

</body>
</html>
Loading

0 comments on commit 4bd1ad2

Please sign in to comment.