Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisala committed Nov 27, 2023
2 parents b076e20 + d0073ce commit 33a76c9
Show file tree
Hide file tree
Showing 22 changed files with 1,019 additions and 673 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@ dependencies {
implementation "org.grails.plugins:scaffolding"
implementation "org.grails.plugins:gsp"
implementation 'commons-io:commons-io:2.6'
implementation "org.grails.plugins:ala-auth:5.1.1"
implementation 'org.pac4j:pac4j-core:5.3.1'
implementation 'org.pac4j:pac4j-http:5.3.1'
implementation "org.grails.plugins:ala-auth:$alaSecurityLibsVersion"
implementation "org.grails.plugins:ala-ws-security-plugin:$alaSecurityLibsVersion"
implementation "au.org.ala:userdetails-service-client:$alaSecurityLibsVersion"

console "org.grails:grails-console"
profile "org.grails.profiles:web-plugin"
Expand Down
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ org.gradle.parallel=true
#grailsWrapperVersion=1.0.0
#gradleWrapperVersion=5.0
assetPipelineVersion=3.4.7
seleniumVersion=4.0.0
seleniumVersion=4.2.0
webdriverBinariesVersion=2.6
seleniumSafariDriverVersion=4.0.0
org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xss2048k -Xmx1024M
exploded=true
enableClover=false
enableJacoco=true
alaSecurityLibsVersion=6.2.0
11 changes: 11 additions & 0 deletions grails-app/conf/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,14 @@ grails:
taglib: none
staticparts: none

userProfile:
userIdAttribute: "username"

---
environments:
test:
server:
port: "8087"
spring:
autoconfigure:
exclude: "au.org.ala.ws.security.AlaWsSecurityConfiguration"
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class ModelService {
value = "'${value}'"
}
} else if(dataModel.name == 'recordedBy' && !value) {
value = "'${userInfoService.getCurrentUser()?.displayName?:''}'"
value = "'${userInfoService.getCurrentUserDisplayName()}'"
}
else if (value) {
value = JavaScriptCodec.ENCODER.encode(value)
Expand Down
122 changes: 80 additions & 42 deletions grails-app/services/au/org/ala/ecodata/forms/UserInfoService.groovy
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package au.org.ala.ecodata.forms

import au.org.ala.web.UserDetails
import org.grails.web.servlet.mvc.GrailsWebRequest
import org.pac4j.core.config.Config
import org.pac4j.core.context.WebContext
import org.pac4j.core.credentials.Credentials
import org.pac4j.core.util.FindBest
import org.pac4j.jee.context.JEEContextFactory
import org.pac4j.http.client.direct.DirectBearerAuthClient
import au.org.ala.ws.security.client.AlaOidcClient
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus

Expand Down Expand Up @@ -36,12 +37,46 @@ class UserInfoService {
@Autowired(required = false)
Config config
@Autowired(required = false)
DirectBearerAuthClient directBearerAuthClient
AlaOidcClient alaOidcClient

static String USER_NAME_HEADER_FIELD = "userName"
static String AUTH_KEY_HEADER_FIELD = "authKey"
static String AUTHORIZATION_HEADER_FIELD = "Authorization"

private static ThreadLocal<UserDetails> _currentUser = new ThreadLocal<UserDetails>()

String getCurrentUserDisplayName() {
getCurrentUser()?.displayName
}

UserDetails getCurrentUser() {
_currentUser.get()
}

/**
* This method gets called by a filter at the beginning of the request (if a userId parameter is on the URL)
* It sets the user details in a thread local for extraction by the audit service.
* @param userId
*/
UserDetails setCurrentUser() {
clearCurrentUser()
UserDetails userDetails = getCurrentUserFromSupportedMethods()

if (userDetails) {
_currentUser.set(userDetails)
} else {
log.warn("Failed to get user details! No details set on thread local.")
}

userDetails
}

def clearCurrentUser() {
if (_currentUser) {
_currentUser.remove()
}
}

/**
* Get User details for the given user name and auth key.
*
Expand All @@ -50,18 +85,13 @@ class UserInfoService {
* @return Map
*
**/
Map getUserFromAuthKey(String username, String key) {
UserDetails getUserFromAuthKey(String username, String key) {
String url = grailsApplication.config.getProperty('mobile.auth.check.url')
Map params = [userName: username, authKey: key]
def result = webService.doPostWithParams(url, params)

if (result.statusCode == HttpStatus.OK.value() && result.resp?.status == 'success') {
params = [userName: username]
url = grailsApplication.config.getProperty('userDetails.url') + "userDetails/getUserDetails"
result = webService.doPostWithParams(url, params)
if (result.statusCode == HttpStatus.OK.value() && result.resp) {
return ['displayName': "${result.resp.firstName} ${result.resp.lastName}", 'userName': result.resp.userName, 'userId': result.resp.userId]
}
return authService.getUserForEmailAddress(username, true)
} else {
log.error("Failed to get user details for parameters: ${params.toString()}")
log.error(result.toString())
Expand All @@ -73,57 +103,65 @@ class UserInfoService {
* @param authorizationHeader
* @return
*/
Map getUserFromJWT(String authorizationHeader = null) {
if((config == null) || (directBearerAuthClient == null))
UserDetails getUserFromJWT(String authorizationHeader = null) {
if((config == null) || (alaOidcClient == null))
return

GrailsWebRequest grailsWebRequest = GrailsWebRequest.lookup()
HttpServletRequest request = grailsWebRequest.getCurrentRequest()
HttpServletResponse response = grailsWebRequest.getCurrentResponse()
if (!authorizationHeader)
authorizationHeader = request?.getHeader(AUTHORIZATION_HEADER_FIELD)
if (authorizationHeader?.startsWith("Bearer")) {
final WebContext context = FindBest.webContextFactory(null, config, JEEContextFactory.INSTANCE).newContext(request, response)
def optCredentials = directBearerAuthClient.getCredentials(context, config.sessionStore)
if (optCredentials.isPresent()) {
Credentials credentials = optCredentials.get()
def optUserProfile = directBearerAuthClient.getUserProfile(credentials, context, config.sessionStore)
if (optUserProfile.isPresent()) {
def userProfile = optUserProfile.get()
return ['displayName': "${userProfile.getAttribute("given_name")} ${userProfile.getAttribute("family_name")}", 'userName': userProfile.getAttribute("email"), 'userId': userProfile.getAttribute("userid")]
try {
GrailsWebRequest grailsWebRequest = GrailsWebRequest.lookup()
HttpServletRequest request = grailsWebRequest.getCurrentRequest()
HttpServletResponse response = grailsWebRequest.getCurrentResponse()
if (!authorizationHeader)
authorizationHeader = request?.getHeader(AUTHORIZATION_HEADER_FIELD)
if (authorizationHeader?.startsWith("Bearer")) {
final WebContext context = FindBest.webContextFactory(null, config, JEEContextFactory.INSTANCE).newContext(request, response)
def optCredentials = alaOidcClient.getCredentials(context, config.sessionStore)
if (optCredentials.isPresent()) {
Credentials credentials = optCredentials.get()
def optUserProfile = alaOidcClient.getUserProfile(credentials, context, config.sessionStore)
if (optUserProfile.isPresent()) {
def userProfile = optUserProfile.get()
String userId = userProfile?.userId ?: userProfile?.getAttribute(grailsApplication.config.getProperty('userProfile.userIdAttribute'))
if (userId) {
return authService.getUserForUserId(userId)
}
}
}
}
} catch (Throwable e) {
log.error("Failed to get user details from JWT", e)
return
}
}

/**
* Get details of the current user either from CAS or lookup to user details server.
* Authentication details are provide in header userName and authKey
* @return Map with following key
* ['displayName': "", 'userName': "", 'userId': ""]
* @return UserDetails
*/
def getCurrentUser() {
UserDetails getCurrentUserFromSupportedMethods() {
def user

// First, check if CAS can get logged in user details
def userDetails = authService.userDetails()
if (userDetails) {
user = ['displayName': "${userDetails.firstName} ${userDetails.lastName}", 'userName': userDetails.userName, 'userId': userDetails.userId]
}
user = userDetails?:null

// Second, check if request has headers to lookup user details.
if (!user) {
GrailsWebRequest request = GrailsWebRequest.lookup()
if (request) {
String authorizationHeader = request?.getHeader(AUTHORIZATION_HEADER_FIELD)
String username = request.getHeader(UserInfoService.USER_NAME_HEADER_FIELD)
String key = request.getHeader(UserInfoService.AUTH_KEY_HEADER_FIELD)

if (authorizationHeader) {
user = getUserFromJWT(authorizationHeader)
} else if (grailsApplication.config.getProperty("mobile.authKeyEnabled", Boolean) && username && key) {
user = getUserFromAuthKey(username, key)
try {
GrailsWebRequest request = GrailsWebRequest.lookup()
if (request) {
String authorizationHeader = request?.getHeader(AUTHORIZATION_HEADER_FIELD)
String username = request.getHeader(UserInfoService.USER_NAME_HEADER_FIELD)
String key = request.getHeader(UserInfoService.AUTH_KEY_HEADER_FIELD)

if (authorizationHeader) {
user = getUserFromJWT(authorizationHeader)
} else if (grailsApplication.config.getProperty("mobile.authKeyEnabled", Boolean) && username && key) {
user = getUserFromAuthKey(username, key)
}
}
} catch (Throwable e) {
log.error("Failed to get user details from JWT or API key", e)
}
}

Expand Down
2 changes: 1 addition & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module.exports = function (config) {
'type':"text",
check: {
global: {
lines: 48.1
lines: 47.6
}
}
},
Expand Down
Loading

0 comments on commit 33a76c9

Please sign in to comment.