diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d8319be27..bf5e958e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ env: jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 @@ -31,6 +31,30 @@ jobs: - run: npm install + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1.0.6 + + - name: Install and start elasticsearch + run: | + curl https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.16.3-amd64.deb -o elasticsearch.deb + sudo dpkg -i --force-confnew elasticsearch.deb + sudo chown -R elasticsearch:elasticsearch /etc/default/elasticsearch + sudo sh -c 'echo ES_JAVA_OPTS=\"-Xmx1g -Xms1g\" >> /etc/default/elasticsearch' + sudo service elasticsearch restart + + - name: Setup required MERIT folders + run: | + sudo mkdir -p /data/fieldcapture/cache + sudo chmod o+xw /data + sudo chmod o+xw /data/fieldcapture + sudo chmod o+xw /data/fieldcapture/cache + + - name: Install and start mongodb + uses: supercharge/mongodb-github-action@1.7.0 + with: + mongodb-version: '5.0' + + - name: Read the biocollect version from the gradle.properties file id: read_property uses: christian-draeger/read-properties@1.1.0 @@ -53,22 +77,28 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: build biocollect before running js unit test to compile dependent js templates - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee + uses: gradle/gradle-build-action@v2.4.0 with: - arguments: build + arguments: _Events env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Run javascript unit tests run: node_modules/karma/bin/karma start karma.conf.js --single-run --browsers ChromeHeadless + - name: Run BioCollect functional tests + run: ./src/main/scripts/runFunctionalTests.sh chromeHeadless /tmp/ecodata feature/cognito + env: + GITHUB_ACTOR: ${{env.GITHUB_ACTOR}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Clean to remove clover instrumentation - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee + uses: gradle/gradle-build-action@v2.4.0 with: arguments: clean - name: Publish the JAR to the repository - uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee + uses: gradle/gradle-build-action@v2.4.0 with: arguments: publish env: diff --git a/build.gradle b/build.gradle index 7a98bae71..b19c731dd 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ buildscript { repositories { mavenLocal() + maven { url "https://plugins.gradle.org/m2/" } maven { url "https://repo.grails.org/grails/core" } maven { url "https://nexus.ala.org.au/content/groups/public/" } } @@ -12,6 +13,8 @@ buildscript { classpath 'com.bertramlabs.plugins:sass-asset-pipeline:3.2.5' classpath 'com.bmuschko:gradle-clover-plugin:3.0.1' classpath 'org.grails.plugins:quartz:2.0.13' + classpath "gradle.plugin.com.github.william-hill-online:wiremock-gradle-plugin:0.4.3" + classpath "com.github.tomakehurst:wiremock-jre8-standalone:2.28.0" } } @@ -27,6 +30,7 @@ apply plugin: "org.grails.grails-web" apply plugin:"com.github.erdi.webdriver-binaries" apply plugin: "com.bertramlabs.asset-pipeline" apply plugin: "org.grails.grails-gsp" +apply plugin: "com.github.william-hill-online.wiremock" if (Boolean.valueOf(enableClover)) { apply from: "${project.projectDir}/gradle/clover.gradle" @@ -81,6 +85,9 @@ dependencies { implementation "org.grails:grails-plugin-url-mappings" implementation "org.grails:grails-plugin-interceptors" implementation "org.grails.plugins:cache" + implementation "org.grails.plugins:cache-ehcache:3.0.0" + runtimeOnly "javax.xml.bind:jaxb-api:2.3.1" + runtimeOnly "org.glassfish.jaxb:jaxb-runtime:2.3.1" implementation "org.grails.plugins:async" implementation "org.grails:grails-async-gpars" implementation "org.grails.plugins:scaffolding" @@ -129,14 +136,15 @@ dependencies { implementation 'org.apache.poi:poi-ooxml-schemas:4.1.2' implementation "org.grails.plugins:ala-admin-plugin:2.3.0" - implementation ("org.grails.plugins:ala-auth:5.1.1") - implementation "org.grails.plugins:ala-ws-security-plugin:4.1.2" - runtimeOnly "org.grails.plugins:ala-bootstrap3:4.2.0" + implementation ("org.grails.plugins:ala-auth:$alaSecurityLibsVersion") + implementation ("org.grails.plugins:ala-ws-plugin:$alaSecurityLibsVersion") + implementation "org.grails.plugins:ala-ws-security-plugin:$alaSecurityLibsVersion" + implementation "au.org.ala:userdetails-service-client:$alaSecurityLibsVersion" + runtimeOnly "org.grails.plugins:ala-bootstrap3:4.1.0" // swagger API implementation 'au.org.ala.plugins:openapi:1.1.0' - implementation "au.org.ala:userdetails-service-client:1.5.0" implementation "org.codehaus.groovy.modules.http-builder:http-builder:0.7.1" implementation 'au.org.ala:ala-cas-client:2.5' runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") @@ -146,7 +154,7 @@ dependencies { if (!Boolean.valueOf(inplace)) { implementation "org.grails.plugins:ala-map-plugin:3.0.1" - implementation "org.grails.plugins:ecodata-client-plugin:6.0" + implementation "org.grails.plugins:ecodata-client-plugin:6.1-COGNITO-SNAPSHOT" } testCompileOnly "org.grails:grails-test-mixins:3.3.0" @@ -154,12 +162,12 @@ dependencies { testImplementation "org.mockito:mockito-core" testImplementation "org.grails:grails-web-testing-support" testImplementation "org.grails.plugins:geb" - testImplementation "org.seleniumhq.selenium:selenium-remote-driver:4.0.0" - testImplementation "org.seleniumhq.selenium:selenium-api:4.0.0" - testImplementation "org.seleniumhq.selenium:selenium-support:4.0.0" - testRuntimeOnly "org.seleniumhq.selenium:selenium-chrome-driver:4.0.0" - testRuntimeOnly "org.seleniumhq.selenium:selenium-firefox-driver:4.0.0" - testCompileOnly "com.github.tomakehurst:wiremock-jre8-standalone:2.28.0" + testImplementation "com.github.tomakehurst:wiremock-jre8-standalone:2.28.0" + testImplementation "org.seleniumhq.selenium:selenium-remote-driver:$seleniumVersion" + testImplementation "org.seleniumhq.selenium:selenium-api:$seleniumVersion" + testImplementation "org.seleniumhq.selenium:selenium-support:$seleniumVersion" + testRuntimeOnly "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion" + testRuntimeOnly "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion" testCompileOnly "com.codeborne:phantomjsdriver:1.3.0" } @@ -236,29 +244,22 @@ tasks.withType(Test) { useJUnitPlatform() } -webdriverBinaries { - if (!System.getenv().containsKey('GITHUB_ACTIONS')) { - chromedriver { - versionRegexp = '.*' - architecture = 'X86_64' - fallbackTo32Bit = true - } - geckodriver '0.30.0' - } -} - tasks.withType(Test) { systemProperty "geb.env", System.getProperty('geb.env') systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest") - if (!System.getenv().containsKey('GITHUB_ACTIONS')) { - systemProperty 'webdriver.chrome.driver', System.getProperty('webdriver.chrome.driver') - systemProperty 'webdriver.gecko.driver', System.getProperty('webdriver.gecko.driver') - } else { - systemProperty 'webdriver.chrome.driver', "${System.getenv('CHROMEWEBDRIVER')}/chromedriver" - systemProperty 'webdriver.gecko.driver', "${System.getenv('GECKOWEBDRIVER')}/geckodriver" + systemProperty 'webdriver.chrome.driver', System.getProperty('webdriver.chrome.driver') + systemProperty 'webdriver.gecko.driver', System.getProperty('webdriver.gecko.driver') + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "full" + showStandardStreams true } } +wiremock { + dir "${project.projectDir}/src/integration-test/resources/wiremock/" + params "--port=8018 --global-response-templating --local-response-templating" +} assets { minifyJs = true diff --git a/gradle.properties b/gradle.properties index b83ac86db..14cb069c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,9 @@ -biocollectVersion=6.6.1-SNAPSHOT +biocollectVersion=6.7-SNAPSHOT grailsVersion=5.1.9 grailsGradlePluginVersion=5.1.5 assetPipelineVersion=3.3.4 +alaSecurityLibsVersion=6.1.0 +seleniumVersion=4.9.0 groovyVersion=3.0.7 gorm.version=7.2.1 org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx2048M diff --git a/grails-app/conf/application.groovy b/grails-app/conf/application.groovy index 57c5f55ea..296b787ad 100644 --- a/grails-app/conf/application.groovy +++ b/grails-app/conf/application.groovy @@ -32,11 +32,13 @@ environments { sender = "biocollect-dev@ala.org.au" debugUI = true loggerLevel = "DEBUG" + auth.baseURL = "https://auth-test.ala.org.au" } test { - debugUI: false - loggerLevel: "DEBUG" + spring.autoconfigure.exclude="au.org.ala.ws.security.AlaWsSecurityConfiguration" + debugUI = false + loggerLevel = "DEBUG" server.port = "8087" grails.host = "http://devt.ala.org.au" serverName = "${grails.host}:${server.port}" @@ -46,15 +48,20 @@ environments { app.default.hub='ala' runWithNoExternalConfig = true wiremock.port = 8018 + grails.config.locations = [] + security.oidc.discoveryUri = "http://localhost:${wiremock.port}/cas/oidc/.well-known" + security.oidc.allowUnsignedIdTokens = true def casBaseUrl = "http://devt.ala.org.au:${wiremock.port}" security.cas.appServerName=serverName security.cas.contextPath= security.cas.casServerName="${casBaseUrl}" + auth.baseURL = "${casBaseUrl}" security.cas.casServerUrlPrefix="${casBaseUrl}/cas" security.cas.loginUrl="${security.cas.casServerUrlPrefix}/login" security.cas.casLoginUrl="${security.cas.casServerUrlPrefix}/login" security.cas.logoutUrl="${security.cas.casServerUrlPrefix}/logout" + security.jwt.discoveryUri="${casBaseUrl}/cas/oidc/.well-known" userDetails.url = "${casBaseUrl}/userdetails/userDetails/" userDetailsSingleUrl = "${userDetails.Url}getUserDetails" userDetailsUrl = "${userDetatails.url}getUserListFull" @@ -66,6 +73,14 @@ environments { ecodata.service.url = 'http://devt.ala.org.au:8080/ws' pdfgen.baseURL = "http://devt.ala.org.au:${wiremock.port}/" api_key='testapikey' + grails.cache.config = { + diskStore { + path '/tmp' + } + defaultCache { + overflowToDisk false + } + } spatial.baseUrl = "http://localhost:${wiremock.port}" spatial.baseURL = "http://localhost:${wiremock.port}" spatial.geoserverUrl= spatial.baseUrl + "/geoserver" @@ -86,9 +101,37 @@ environments { sender = "biocollect-local@ala.org.au" debugUI = false loggerLevel = "INFO" + auth.baseURL = "https://auth.ala.org.au" } } +casUrl = "${auth.baseURL}/cas/logout" +appUrl = grails.serverURL + +security.cas.enabled = false +security.cas.uriExclusionFilterPattern = ['/assets/.*','/uploads/.*'] +security.cas.uriFilterPattern = [] +security.cas.readOnlyOfficerRole= "ROLE_FC_READ_ONLY" +security.cas.alaAdminRole = "ROLE_ADMIN" +security.cas.officerRole = "ROLE_FC_OFFICER" +security.cas.adminRole = "ROLE_FC_ADMIN" +security.cas.casServerName= "${auth.baseURL}" +security.cas.casServerLoginUrl= "${auth.baseURL}/cas/login" +security.cas.casServerUrlPrefix= "${auth.baseURL}/cas" +security.cas.logoutUrl= "${security.cas.casServerUrlPrefix}/logout" +security.cas.loginUrl= "${security.cas.casServerUrlPrefix}/login" + +security.oidc.enabled= true +security.oidc.discoveryUri= "${auth.baseURL}/cas/oidc/.well-known" +security.oidc.clientId= "changeMe" +security.oidc.secret= "changeMe" +security.oidc.scope= "openid,profile,email,ala,roles" +security.oidc.allowUnsignedIdTokens= true + +security.jwt.enabled= true +security.jwt.discoveryUri= "${auth.baseURL}/cas/oidc/.well-known" +security.jwt.fallbackToLegacyBehaviour= true + dataAccessMethods = [ "oasrdfs", "oaordfs", diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml index e27dfd937..b2aced28e 100644 --- a/grails-app/conf/application.yml +++ b/grails-app/conf/application.yml @@ -19,39 +19,6 @@ app: userId: "X-ALA-userId" hostName: "X-ALA-hostname" -auth: - baseURL: "https://auth.ala.org.au" - -casUrl: "${auth.baseURL}/cas/logout" -appUrl: "http://devt.ala.org.au:8087/" - -security: - cas: - enabled: false - uriExclusionFilterPattern: ['/assets/.*','/uploads/.*'] - uriFilterPattern: [] - readOnlyOfficerRole: "ROLE_FC_READ_ONLY" - alaAdminRole : "ROLE_ADMIN" - officerRole : "ROLE_FC_OFFICER" - adminRole : "ROLE_FC_ADMIN" - casServerName: "${auth.baseURL}" - casServerLoginUrl: "${auth.baseURL}/cas/login" - casServerUrlPrefix: "${auth.baseURL}/cas" - logoutUrl: "${security.cas.casServerUrlPrefix}/logout" - loginUrl: "${security.cas.casServerUrlPrefix}/login" - - oidc: - enabled: true - discoveryUri: "${auth.baseURL}/cas/oidc/.well-known" - clientId: "changeMe" - secret: "changeMe" - scope: "openid,profile,email,ala,roles" - allowUnsignedIdTokens: true - jwt: - enabled: true - discoveryUri: "${auth.baseURL}/cas/oidc/.well-known" - fallbackToLegacyBehaviour: true - #External URL aekosEnabled: false enableReporting: true @@ -169,15 +136,8 @@ grails: codegen: defaultPackage: au.org.ala.biocollect cache: - defaults: - eternal: true - overflowToDisk: false - maxElementsInMemory: 20000 - timeToLiveSeconds: 3600 - name: 'vocabListCache' ehcache: - cacheManagerName : "${appName}-ehcache" - reloadable : false + ehcacheXmlLocation: 'classpath:biocollect-ehcache.xml' mime: file: extensions: true @@ -342,5 +302,11 @@ bootstrap4: skin: layout: "bs4" +--- + +grails: + cors: + enabled: true + fathom: enabled: true \ No newline at end of file diff --git a/grails-app/conf/biocollect-ehcache.xml b/grails-app/conf/biocollect-ehcache.xml new file mode 100644 index 000000000..85d7da2e9 --- /dev/null +++ b/grails-app/conf/biocollect-ehcache.xml @@ -0,0 +1,43 @@ + + + + + + + 1 + + + 2000 + + + + + + 1 + + + 2000 + + + + + + 1 + + + 2000 + + + + + + 1 + + + 20000 + + + + \ No newline at end of file diff --git a/grails-app/controllers/au/org/ala/biocollect/AclFilterInterceptor.groovy b/grails-app/controllers/au/org/ala/biocollect/AclFilterInterceptor.groovy index f01b70a42..2769fe5d9 100644 --- a/grails-app/controllers/au/org/ala/biocollect/AclFilterInterceptor.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/AclFilterInterceptor.groovy @@ -3,12 +3,14 @@ package au.org.ala.biocollect import au.org.ala.biocollect.merit.PreAuthorise import au.org.ala.biocollect.merit.ProjectController import au.org.ala.biocollect.merit.RoleService +import au.org.ala.ecodata.forms.UserInfoService import grails.converters.JSON import org.springframework.http.HttpStatus class AclFilterInterceptor { int order = 3 def userService, projectService, roleService + UserInfoService userInfoService def roles = [] @@ -21,6 +23,8 @@ class AclFilterInterceptor { boolean before() { + userInfoService.setCurrentUser() + if (!controllerName) return true def controller = grailsApplication.getArtefactByLogicalPropertyName("Controller", controllerName) @@ -38,7 +42,7 @@ class AclFilterInterceptor { } def roles = roleService.getAugmentedRoles() - def userId = userService.getCurrentUserId(request) + def userId = userService.getCurrentUserId() def projectId = params.projectId if(!projectId){ @@ -205,6 +209,6 @@ class AclFilterInterceptor { } void afterView() { - // no-op + userInfoService.clearCurrentUser() } } diff --git a/grails-app/controllers/au/org/ala/biocollect/BioActivityController.groovy b/grails-app/controllers/au/org/ala/biocollect/BioActivityController.groovy index 12fb36b82..bd8f563c5 100644 --- a/grails-app/controllers/au/org/ala/biocollect/BioActivityController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/BioActivityController.groovy @@ -149,7 +149,7 @@ class BioActivityController { log.debug("projectId = ${projectId}") log.debug((postBody as JSON).toString()) - String userId = userService.getCurrentUserId(request) + String userId = userService.getCurrentUserId() if (!userId) { flash.message = "Access denied: User has not been authenticated." response.status = 401 @@ -341,7 +341,7 @@ class BioActivityController { private def addActivity(String id, boolean mobile = false) { - String userId = userService.getCurrentUserId(request) + String userId = userService.getCurrentUserId() Map pActivity = projectActivityService.get(id, "all") String projectId = pActivity?.projectId String type = pActivity?.pActivityFormName @@ -378,7 +378,7 @@ class BioActivityController { } private editActivity(String id, boolean mobile = false){ - String userId = userService.getCurrentUserId(request) + String userId = userService.getCurrentUserId() def activity = activityService.get(id) String projectId = activity?.projectId def model = [:] @@ -463,7 +463,7 @@ class BioActivityController { @Path("/ws/bioactivity/delete/{id}") def delete(String id) { def activity = activityService.get(id) - String userId = userService.getCurrentUserId(request) + String userId = userService.getCurrentUserId() Map result @@ -528,7 +528,7 @@ class BioActivityController { * @return */ def index(String id) { - String userId = userService.getCurrentUserId(request) + String userId = userService.getCurrentUserId() def activity = activityService.get(id, params?.version, userId, true) if (activity.error){ redirect(controller: "error", action:'response404', params: [status: 404, errMsg: activity.error]) @@ -768,7 +768,7 @@ class BioActivityController { private GrailsParameterMap constructDefaultSearchParams(Map params) { GrailsParameterMap queryParams = new GrailsParameterMap([:], request) Map parsed = commonService.parseParams(params) - parsed.userId = userService.getCurrentUserId(parsed.mobile ? request : null) + parsed.userId = userService.getCurrentUserId() parsed.each { key, value -> if (value != null && value) { @@ -1494,7 +1494,7 @@ class BioActivityController { ) @Path("ws/bioactivity/data/{id}") def getOutputForActivity(String id){ - String userId = userService.getCurrentUserId(request) + String userId = userService.getCurrentUserId() def activity = activityService.get(id) String projectId = activity?.projectId def model = [:] @@ -1578,7 +1578,7 @@ class BioActivityController { ) @Path("ws/bioactivity/model/{id}") def getActivityModel(String id){ - String userId = userService.getCurrentUserId(request) + String userId = userService.getCurrentUserId() Map model = [:] if(userId){ diff --git a/grails-app/controllers/au/org/ala/biocollect/CommentController.groovy b/grails-app/controllers/au/org/ala/biocollect/CommentController.groovy index c8e73070d..c10ad0e2d 100644 --- a/grails-app/controllers/au/org/ala/biocollect/CommentController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/CommentController.groovy @@ -2,21 +2,22 @@ package au.org.ala.biocollect import au.org.ala.biocollect.merit.BaseController import au.org.ala.biocollect.merit.CommonService -import au.org.ala.web.AuthService +import au.org.ala.biocollect.merit.UserService import au.org.ala.web.NoSSO import au.org.ala.web.SSO import static org.apache.http.HttpStatus.SC_BAD_REQUEST + @SSO class CommentController extends BaseController { CommonService commonService CommentService commentService - AuthService authService + UserService userService def create() { Map json = commonService.parseParams(params) - json.userId = authService.getUserId() + json.userId = userService.getCurrentUserId() if (!json.userId|| !json.entityId || !json.entityType || !json.text){ response.sendError(SC_BAD_REQUEST, 'Missing userId, text, entityId and/or entityType') } else { @@ -28,7 +29,7 @@ class CommentController extends BaseController { def update() { def json = commonService.parseParams(params); - json.userId = authService.getUserId() + json.userId = userService.getCurrentUserId() if (!json.userId|| !json.entityId || !json.entityType || !json.text){ response.sendError(SC_BAD_REQUEST, 'Missing userId, text, entityId and/or entityType') } else if (json) { @@ -39,7 +40,7 @@ class CommentController extends BaseController { def delete() { def json = commonService.parseParams(params); - json.userId = authService.getUserId() + json.userId = userService.getCurrentUserId() if (!json.id || !json.userId){ response.sendError(SC_BAD_REQUEST, 'Missing userId and/or comment id') } else if (json) { diff --git a/grails-app/controllers/au/org/ala/biocollect/OrganisationController.groovy b/grails-app/controllers/au/org/ala/biocollect/OrganisationController.groovy index 9489748b1..46e95d42b 100644 --- a/grails-app/controllers/au/org/ala/biocollect/OrganisationController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/OrganisationController.groovy @@ -1,10 +1,9 @@ package au.org.ala.biocollect import au.org.ala.biocollect.merit.RoleService -import au.org.ala.web.AuthService -import grails.converters.JSON -import au.org.ala.web.SSO import au.org.ala.web.NoSSO +import au.org.ala.web.SSO +import grails.converters.JSON /** * Processes requests relating to organisations */ @@ -14,7 +13,6 @@ class OrganisationController { static allowedMethods = [ajaxDelete: "POST", delete: "POST", ajaxUpdate: "POST"] def organisationService, searchService, documentService, userService, roleService, commonService, webService - AuthService authService // Simply forwards to the list view @NoSSO @@ -203,7 +201,7 @@ class OrganisationController { * @return */ def searchMyOrg(Integer offset, Integer max, String searchTerm, String sort) { - String userId = authService.getUserId() + String userId = userService.getCurrentUserId() render organisationService.search(offset, max, searchTerm, sort, userId) as JSON } diff --git a/grails-app/controllers/au/org/ala/biocollect/RecordController.groovy b/grails-app/controllers/au/org/ala/biocollect/RecordController.groovy index 6de8eb5a1..541d9bacb 100644 --- a/grails-app/controllers/au/org/ala/biocollect/RecordController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/RecordController.groovy @@ -41,7 +41,7 @@ class RecordController { } def listProjectActivityAndUserRecords(String id) { - def userId = userService.getCurrentUserId(request) + def userId = userService.getCurrentUserId() if (!userId) { return forbidden("Sorry mate, can't help you.") } diff --git a/grails-app/controllers/au/org/ala/biocollect/merit/AdminController.groovy b/grails-app/controllers/au/org/ala/biocollect/merit/AdminController.groovy index 038f5d42f..fb2b73bc7 100644 --- a/grails-app/controllers/au/org/ala/biocollect/merit/AdminController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/merit/AdminController.groovy @@ -2,6 +2,8 @@ package au.org.ala.biocollect.merit import au.org.ala.biocollect.merit.hub.HubSettings import grails.converters.JSON +import org.grails.plugin.cache.GrailsCacheManager + //import grails.plugin.cache.CacheEvict import org.springframework.cache.annotation.CacheEvict import grails.util.Environment @@ -17,7 +19,6 @@ class AdminController { def cacheService def metadataService - def authService def projectService def importService def adminService @@ -29,8 +30,10 @@ class AdminController { def documentService def projectActivityService def webService + UserService userService grails.core.GrailsApplication grailsApplication def roleService + GrailsCacheManager grailsCacheManager def index() {} @@ -44,7 +47,7 @@ class AdminController { * @return */ def users() { - def user = authService.userDetails() + def user = userService.getUser() def projects = projectService.list(true) def roles = metadataService.getAccessLevels().collect { it.name @@ -60,7 +63,7 @@ class AdminController { @PreAuthorise(accessLevel = 'alaAdmin', redirectController = "admin") def bulkLoadUserPermissions() { - def user = authService.userDetails() + def user = userService.getUser() [user:user] } @@ -76,7 +79,7 @@ class AdminController { @PreAuthorise(accessLevel = 'alaAdmin', redirectController = "admin") def uploadUserPermissionsCSV() { - def user = authService.userDetails() + def user = userService.getUser() def results @@ -516,4 +519,18 @@ class AdminController { render text: [message:'Species rematch initiated.'] as JSON, contentType: 'application/json' } + @PreAuthorise(accessLevel = 'alaAdmin', redirectController = "admin") + def cacheManagement() { + [cacheRegions:grailsCacheManager.getCacheNames()] + } + + @PreAuthorise(accessLevel = 'alaAdmin', redirectController = "admin") + def clearCache() { + if (params.cache) { + grailsCacheManager.getCache(params.cache).clear() + } + + redirect action: 'cacheManagement' + } + } diff --git a/grails-app/controllers/au/org/ala/biocollect/merit/ImageController.groovy b/grails-app/controllers/au/org/ala/biocollect/merit/ImageController.groovy index 5d77588a3..a467c8982 100644 --- a/grails-app/controllers/au/org/ala/biocollect/merit/ImageController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/merit/ImageController.groovy @@ -212,7 +212,7 @@ class ImageController { @Path("ws/attachment/upload") @NoSSO def upload() { - def user = userService.getCurrentUserId(request) + def user = userService.getCurrentUserId() def result = [] if (request.respondsTo('getFile') && user) { diff --git a/grails-app/controllers/au/org/ala/biocollect/merit/ProjectController.groovy b/grails-app/controllers/au/org/ala/biocollect/merit/ProjectController.groovy index 9889fbd8f..fe2a2f406 100644 --- a/grails-app/controllers/au/org/ala/biocollect/merit/ProjectController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/merit/ProjectController.groovy @@ -10,6 +10,7 @@ import au.org.ala.plugins.openapi.Path import au.org.ala.web.AuthService import au.org.ala.web.NoSSO import au.org.ala.web.SSO +import au.org.ala.web.UserDetails import grails.converters.JSON import grails.web.servlet.mvc.GrailsParameterMap import io.swagger.v3.oas.annotations.Operation @@ -1314,7 +1315,7 @@ class ProjectController { } def auditMessageDetails() { - String userId = authService.getUserId() + String userId = userService.getCurrentUserId() String projectId = params.projectId String compareId= params.compareId String skin @@ -1342,7 +1343,7 @@ class ProjectController { } def getAuditMessagesForProject(){ - String userId = authService.getUserId() + String userId = userService.getCurrentUserId() String projectId = params.id Boolean isAdmin = projectService.isUserAdminForProject(userId, projectId) if(isAdmin) { @@ -1383,7 +1384,7 @@ class ProjectController { Map payload = request.JSON payload.max = payload.max ?: 10; payload.offset = payload.offset ?: 0; - payload.userId = authService.getUserId() + payload.userId = userService.getCurrentUserId() payload.order = payload.order ?: 'DESC'; payload.sort = payload.sort ?: 'lastUpdated'; payload.fq = payload.fq ?: [] diff --git a/grails-app/controllers/au/org/ala/biocollect/merit/SiteController.groovy b/grails-app/controllers/au/org/ala/biocollect/merit/SiteController.groovy index d88995389..de1c37ea8 100644 --- a/grails-app/controllers/au/org/ala/biocollect/merit/SiteController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/merit/SiteController.groovy @@ -3,10 +3,10 @@ package au.org.ala.biocollect.merit import au.org.ala.biocollect.swagger.model.SiteAjaxUpdate import au.org.ala.biocollect.swagger.model.SiteCreateUpdateResponse import au.org.ala.plugins.openapi.Path -import au.org.ala.web.AuthService import au.org.ala.web.NoSSO import au.org.ala.web.SSO import grails.converters.JSON +import grails.web.servlet.mvc.GrailsParameterMap import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.enums.ParameterIn @@ -18,7 +18,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.security.SecurityRequirement import org.apache.commons.lang.StringUtils import org.apache.http.HttpStatus -import grails.web.servlet.mvc.GrailsParameterMap + import static javax.servlet.http.HttpServletResponse.SC_CONFLICT import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT @@ -28,7 +28,6 @@ class SiteController { def siteService, projectService, projectActivityService, activityService, metadataService, userService, searchService, importService, webService - AuthService authService CommonService commonService static defaultAction = "index" @@ -131,7 +130,7 @@ class SiteController { redirect(controller: 'home', action: 'index') } else { String projectIds = result.site.projects.toList().join(',') - String userId = authService.getUserId() + String userId = userService.getCurrentUserId() result.userCanEdit = projectService.isUserEditorForProjects(userId, projectIds) result } @@ -544,7 +543,7 @@ class SiteController { @NoSSO def ajaxUpdate(String id) { def result = [:] - String userId = userService.getCurrentUserId(request) + String userId = userService.getCurrentUserId() def postBody = request.JSON Boolean isCreateSiteRequest = !id @@ -735,7 +734,7 @@ class SiteController { List results if (params.id) { GrailsParameterMap mParams = new GrailsParameterMap(commonService.parseParams(params), request); - mParams.userId = authService.getUserId() + mParams.userId = userService.getCurrentUserId() try { results = siteService.getImages(mParams) render(text: results as JSON, contentType: 'application/json') @@ -760,7 +759,7 @@ class SiteController { Map results if (params.siteId && params.poiId) { GrailsParameterMap mParams = new GrailsParameterMap(commonService.parseParams(params), request); - mParams.userId = authService.getUserId() + mParams.userId = userService.getCurrentUserId() try { results = siteService.getPoiImages(mParams) render(text: results as JSON, contentType: 'application/json') diff --git a/grails-app/controllers/au/org/ala/biocollect/merit/UserController.groovy b/grails-app/controllers/au/org/ala/biocollect/merit/UserController.groovy index 6b9200f56..0e0048c71 100644 --- a/grails-app/controllers/au/org/ala/biocollect/merit/UserController.groovy +++ b/grails-app/controllers/au/org/ala/biocollect/merit/UserController.groovy @@ -7,7 +7,7 @@ import au.org.ala.web.SSO */ @SSO class UserController { - def userService, authService, projectService, organisationService + def userService, projectService, organisationService /** * Default view for user controller - show user dashboard page. @@ -62,7 +62,7 @@ class UserController { String userId = params.userId String projectId = params.entityId String role = params.role - def adminUser = authService.userDetails() + def adminUser = userService.getUser() if (adminUser && userId && projectId && role) { if (role == 'caseManager' && !userService.userIsSiteAdmin()) { @@ -81,7 +81,7 @@ class UserController { String userId = params.userId String organisationId = params.entityId String role = params.role - def adminUser = authService.userDetails() + def adminUser = userService.getUser() if (adminUser && userId && organisationId && role) { if (role == 'caseManager' && !userService.userIsSiteAdmin()) { @@ -105,7 +105,7 @@ class UserController { String userId = params.userId String role = params.role String projectId = params.entityId - def adminUser = authService.userDetails() + def adminUser = userService.getUser() if (adminUser && projectId && role && userId) { if (projectService.isUserAdminForProject(adminUser.userId, projectId)) { @@ -127,7 +127,7 @@ class UserController { String userId = params.userId String role = params.role String organisationId = params.entityId - def adminUser = authService.userDetails() + def adminUser = userService.getUser() if (adminUser && organisationId && role && userId) { if (organisationService.isUserAdminForOrganisation(organisationId)) { @@ -148,7 +148,7 @@ class UserController { def viewPermissionsForUserId() { String userId = params.userId - if (authService.userDetails() && (authService.userInRole(grailsApplication.config.security.cas.alaAdminRole) || authService.userInRole(grailsApplication.config.security.cas.officerRole)) && userId) { + if (userService.getUser() && (userService.userInRole(grailsApplication.config.security.cas.alaAdminRole) || userService.userInRole(grailsApplication.config.security.cas.officerRole)) && userId) { render userService.getProjectsForUserId(userId) as JSON } else if (!userId) { render status:400, text: 'Required params not provided: userId, role, projectId' diff --git a/grails-app/services/au/org/ala/biocollect/CommentService.groovy b/grails-app/services/au/org/ala/biocollect/CommentService.groovy index 876d2e70f..64dab97c1 100644 --- a/grails-app/services/au/org/ala/biocollect/CommentService.groovy +++ b/grails-app/services/au/org/ala/biocollect/CommentService.groovy @@ -5,7 +5,6 @@ class CommentService { def webService def grailsApplication def userService - def authService def addComment(data) { webService.doPost(grailsApplication.config.ecodata.service.url + "/comment",data) @@ -29,7 +28,7 @@ class CommentService { Boolean admin = userService.userIsAlaAdmin(); def response = webService.doGet(grailsApplication.config.ecodata.service.url + "/comment", data) if(response?.resp){ - response.resp['userId'] = authService.getUserId() + response.resp['userId'] = userService.getCurrentUserId() Map privilege = webService.doGet(grailsApplication.config.ecodata.service.url + "/comment/canUserEditOrDeleteComment", [userId:response.resp['userId'], entityId:data['entityId'], entityType: data['entityType']] ).resp; // this is used by knockout to decide if edit/delete should be shown. diff --git a/grails-app/services/au/org/ala/biocollect/ProjectActivityService.groovy b/grails-app/services/au/org/ala/biocollect/ProjectActivityService.groovy index b29567ae4..bfb276626 100644 --- a/grails-app/services/au/org/ala/biocollect/ProjectActivityService.groovy +++ b/grails-app/services/au/org/ala/biocollect/ProjectActivityService.groovy @@ -2,7 +2,6 @@ package au.org.ala.biocollect import au.org.ala.biocollect.merit.* import au.org.ala.biocollect.merit.hub.HubSettings -import au.org.ala.web.AuthService import org.springframework.context.MessageSource import java.nio.file.Files @@ -33,7 +32,6 @@ class ProjectActivityService { MetadataService metadataService MessageSource messageSource UtilService utilService - AuthService authService CacheService cacheService SettingService settingService diff --git a/grails-app/services/au/org/ala/biocollect/merit/ProjectService.groovy b/grails-app/services/au/org/ala/biocollect/merit/ProjectService.groovy index 416c89603..81422ff75 100644 --- a/grails-app/services/au/org/ala/biocollect/merit/ProjectService.groovy +++ b/grails-app/services/au/org/ala/biocollect/merit/ProjectService.groovy @@ -5,6 +5,7 @@ import au.org.ala.biocollect.OrganisationService import au.org.ala.biocollect.merit.hub.HubSettings import grails.converters.JSON import org.springframework.context.MessageSource +import au.org.ala.web.UserDetails class ProjectService { @@ -663,10 +664,10 @@ class ProjectService { } - public JSON userProjects(UserDetails user) { + JSON userProjects(UserDetails user) { if (user) { - def projects = userService.getProjectsForUserId(8443) - def starredProjects = userService.getStarredProjectsForUserId(8443) + def projects = userService.getProjectsForUserId(user?.userId) + def starredProjects = userService.getStarredProjectsForUserId(user?.userId) ['active': projects, 'starred': starredProjects] as JSON; } else { [:] as JSON diff --git a/grails-app/services/au/org/ala/biocollect/merit/UserService.groovy b/grails-app/services/au/org/ala/biocollect/merit/UserService.groovy index 7c9c5dc9e..2a1ac8f9e 100644 --- a/grails-app/services/au/org/ala/biocollect/merit/UserService.groovy +++ b/grails-app/services/au/org/ala/biocollect/merit/UserService.groovy @@ -3,6 +3,7 @@ package au.org.ala.biocollect.merit import au.org.ala.biocollect.merit.hub.HubSettings import au.org.ala.ecodata.forms.UserInfoService import au.org.ala.userdetails.UserDetailsFromIdListResponse +import au.org.ala.web.UserDetails class UserService { def grailsApplication, authService, webService @@ -28,19 +29,12 @@ class UserService { getUser()?.displayName?:"" } - def getCurrentUserId(request = null) { - userInfoService.getCurrentUser()?.userId + def getCurrentUserId() { + getUser()?.userId } - public UserDetails getUser() { - def u = authService.userDetails() - def user - - if (u?.userId) { - user = new UserDetails(u.getDisplayName(), u.email, u.userId) - } - - return user + UserDetails getUser() { + userInfoService.getCurrentUser() } /** @@ -67,23 +61,23 @@ class UserService { } def userInRole(role) { - authService.userInRole(role) + userInfoService.getCurrentUser()?.hasRole(role) } def userIsSiteAdmin() { - authService.userInRole(grailsApplication.config.security.cas.officerRole) || authService.userInRole(grailsApplication.config.security.cas.adminRole) || authService.userInRole(grailsApplication.config.security.cas.alaAdminRole) + userInRole(grailsApplication.config.security.cas.officerRole) || userInRole(grailsApplication.config.security.cas.adminRole) || userInRole(grailsApplication.config.security.cas.alaAdminRole) } Boolean userIsAlaAdmin() { - authService.userInRole(grailsApplication.config.security.cas.alaAdminRole) + userInRole(grailsApplication.config.security.cas.alaAdminRole) } def userIsAlaOrFcAdmin() { - authService.userInRole(grailsApplication.config.security.cas.adminRole) || authService.userInRole(grailsApplication.config.security.cas.alaAdminRole) + userInRole(grailsApplication.config.security.cas.adminRole) || userInRole(grailsApplication.config.security.cas.alaAdminRole) } def userHasReadOnlyAccess() { - authService.userInRole(grailsApplication.config.security.cas.readOnlyOfficerRole) + userInRole(grailsApplication.config.security.cas.readOnlyOfficerRole) } def getRecentEditsForUserId(userId) { diff --git a/grails-app/services/au/org/ala/biocollect/merit/WebService.groovy b/grails-app/services/au/org/ala/biocollect/merit/WebService.groovy index 4baab4571..4beb1453c 100644 --- a/grails-app/services/au/org/ala/biocollect/merit/WebService.groovy +++ b/grails-app/services/au/org/ala/biocollect/merit/WebService.groovy @@ -25,7 +25,7 @@ import org.grails.web.converters.exceptions.ConverterException import grails.web.http.HttpHeaders import org.springframework.http.MediaType import org.springframework.web.multipart.MultipartFile - +import au.org.ala.ws.tokens.TokenService import javax.servlet.http.Cookie import javax.servlet.http.HttpServletResponse import java.nio.charset.StandardCharsets @@ -43,6 +43,7 @@ class WebService { } def grailsApplication + TokenService tokenService def get(String url, boolean includeUserId) { def conn = null @@ -70,13 +71,13 @@ class WebService { URLConnection conn = new URL(url).openConnection() def readTimeout = timeout?:defaultTimeout() - conn.setConnectTimeout(grailsApplication.config.webservice.connectTimeout as int) + conn.setConnectTimeout(grailsApplication.config.getProperty("webservice.connectTimeout", Integer)) conn.setReadTimeout(readTimeout) addHubUrlPath(conn) if (includeUserId) { def user = getUserService().getUser() if (user) { - conn.setRequestProperty(grailsApplication.config.app.http.header.userId, user.userId) + conn.setRequestProperty(grailsApplication.config.getProperty("app.http.header.userId", String), user.userId) } } conn @@ -85,7 +86,7 @@ class WebService { URLConnection addHubUrlPath (URLConnection conn) { def hostName = grailsApplication.config.getProperty('grails.serverURL', String) if (hostName) { - conn.setRequestProperty(grailsApplication.config.app.http.header.hostName, hostName) + conn.setRequestProperty(grailsApplication.config.getProperty("app.http.header.hostName", String), hostName) } conn @@ -94,7 +95,7 @@ class WebService { Map addHubUrlPath (Map headers) { def hostName = grailsApplication.config.getProperty('grails.serverURL', String) if (hostName) { - headers[grailsApplication.config.app.http.header.hostName] = hostName + headers[grailsApplication.config.getProperty("app.http.header.hostName", String)] = hostName } headers @@ -112,7 +113,7 @@ class WebService { conn.setReadTimeout(readTimeout) if (includeApiKey) { - conn.setRequestProperty("Authorization", grailsApplication.config.api_key); + conn.setRequestProperty("Authorization", grailsApplication.config.getProperty("api_key")) } def headers = [HttpHeaders.CONTENT_DISPOSITION, HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.LAST_MODIFIED, HttpHeaders.ETAG] @@ -153,7 +154,7 @@ class WebService { conn.setDoOutput ( true ); if (includeApiKey) { - conn.setRequestProperty("Authorization", grailsApplication.config.api_key); + conn.setRequestProperty("Authorization", grailsApplication.config.getProperty("api_key")) } OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream(), charEncoding) @@ -181,13 +182,17 @@ class WebService { def get(String url) { return get(url, true) } + + String getAuthHeader() { + tokenService.getAuthToken(false).toAuthorizationHeader() + } def getJson(String url, Integer timeout = null, boolean includeApiKey = false, boolean includeUserId = true) { def conn = null try { conn = configureConnection(url, includeUserId, timeout) if (includeApiKey) { - conn.setRequestProperty("Authorization", grailsApplication.config.api_key); + conn.setRequestProperty("Authorization", grailsApplication.config.getProperty("api_key")) } conn.setRequestProperty(ACCEPT, MediaType.APPLICATION_JSON_VALUE) def json = responseText(conn) @@ -247,7 +252,7 @@ class WebService { conn.setRequestMethod("POST") conn.setDoOutput(true) conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - conn.setRequestProperty("Authorization", grailsApplication.config.api_key); + conn.setRequestProperty("Authorization", grailsApplication.config.getProperty("api_key")) addHubUrlPath(conn) def user = getUserService().getUser() @@ -282,8 +287,8 @@ class WebService { try { conn = new URL(url).openConnection() conn.setDoOutput(true) - conn.setRequestProperty("Content-Type", "application/json;charset=${charEncoding}"); - conn.setRequestProperty("Authorization", grailsApplication.config.api_key); + conn.setRequestProperty("Content-Type", "application/json;charset=${charEncoding}") + conn.setRequestProperty("Authorization", grailsApplication.config.getProperty("api_key")) addHubUrlPath(conn) def user = getUserService().getUser() @@ -318,7 +323,7 @@ class WebService { conn.setRequestMethod("PUT") conn.setDoOutput(true) conn.setRequestProperty("Content-Type", "application/json;charset=${charEncoding}"); - conn.setRequestProperty("Authorization", grailsApplication.config.api_key); + conn.setRequestProperty("Authorization", grailsApplication.config.getProperty("api_key")) addHubUrlPath(conn) def user = getUserService().getUser() @@ -364,7 +369,7 @@ class WebService { conn.setDoOutput(true) conn.setRequestMethod("GET") conn.setRequestProperty("Content-Type", "${APPLICATION_JSON};charset=${StandardCharsets.UTF_8.toString()}"); - conn.setRequestProperty("Authorization", grailsApplication.config.api_key); + conn.setRequestProperty("Authorization", grailsApplication.config.getProperty("api_key")) addHubUrlPath(conn) def user = getUserService().getUser() @@ -393,7 +398,7 @@ class WebService { try { conn = new URL(url).openConnection() conn.setRequestMethod("DELETE") - conn.setRequestProperty("Authorization", grailsApplication.config.api_key); + conn.setRequestProperty("Authorization", grailsApplication.config.getProperty("api_key")) addHubUrlPath(conn) def user = getUserService().getUser() @@ -461,7 +466,7 @@ class WebService { } addHubUrlPath(headers) - headers.'Authorization' = grailsApplication.config.api_key + headers."Authorization" = grailsApplication.config.getProperty("api_key") if (user) { headers[grailsApplication.config.app.http.header.userId] = user.userId } diff --git a/grails-app/taglib/au/org/ala/biocollect/TemplateTagLib.groovy b/grails-app/taglib/au/org/ala/biocollect/TemplateTagLib.groovy index b4a12175b..d60a4f04f 100644 --- a/grails-app/taglib/au/org/ala/biocollect/TemplateTagLib.groovy +++ b/grails-app/taglib/au/org/ala/biocollect/TemplateTagLib.groovy @@ -356,7 +356,7 @@ class TemplateTagLib { case 'biocacheexplorer': String fq = '' if(request.forwardURI?.contains('/bioActivity/myProjectRecords')){ - fq = "&fq=alau_user_id:${userService.getCurrentUserId(request)}"; + fq = "&fq=alau_user_id:${userService.getCurrentUserId()}"; } url = grailsApplication.config.biocache.baseURL + '/occurrences/search?q=*:*&fq=(data_resource_uid:dr364)' + fq @@ -402,6 +402,6 @@ class TemplateTagLib { } private String getCurrentURL(Map hubConfig){ - grailsApplication.config.getProperty("grails.serverURL") + request.forwardURI + "?hub=" + hubConfig.urlPath + g.createLink(absolute: true, uri: '/').toString() } } diff --git a/grails-app/views/admin/cacheManagement.gsp b/grails-app/views/admin/cacheManagement.gsp new file mode 100644 index 000000000..a3f43b0bf --- /dev/null +++ b/grails-app/views/admin/cacheManagement.gsp @@ -0,0 +1,23 @@ + + + + + Cache Management | Admin | Data capture | Atlas of Living Australia + + + + +Caches +

Caches Management

+
+ + +
+ + + diff --git a/grails-app/views/layouts/adminLayout.gsp b/grails-app/views/layouts/adminLayout.gsp index bb2cfff0f..7cd24e29b 100644 --- a/grails-app/views/layouts/adminLayout.gsp +++ b/grails-app/views/layouts/adminLayout.gsp @@ -27,7 +27,7 @@ - +
diff --git a/karma.conf.js b/karma.conf.js index b38a327ff..8cf246d5f 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -41,7 +41,9 @@ module.exports = function (config) { 'grails-app/assets/javascripts/MapUtilities.js', 'https://cdn.jsdelivr.net/gh/AtlasOfLivingAustralia/ecodata-client-plugin/grails-app/assets/vendor/select2/4.0.3/js/select2.full.js', 'https://cdn.jsdelivr.net/gh/AtlasOfLivingAustralia/ecodata-client-plugin/grails-app/assets/vendor/typeahead/0.11.1/bloodhound.js', + 'https://cdn.jsdelivr.net/gh/AtlasOfLivingAustralia/ecodata-client-plugin/grails-app/assets/vendor/expr-eval/2.0.2/bundle.js', 'https://cdn.jsdelivr.net/gh/AtlasOfLivingAustralia/ecodata-client-plugin/grails-app/assets/javascripts/speciesModel.js', + 'https://cdn.jsdelivr.net/gh/AtlasOfLivingAustralia/ecodata-client-plugin/grails-app/assets/javascripts/forms.js', 'grails-app/assets/javascripts/projectActivityInfo.js', 'grails-app/assets/javascripts/projectActivity.js', 'grails-app/assets/javascripts/projectActivities.js', diff --git a/package-lock.json b/package-lock.json index ec2b6e796..8f705dc1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "6.0", "devDependencies": { "@metahub/karma-jasmine-jquery": "^2.0.1", - "chromedriver": "^110.0.0", + "chromedriver": "^117.0.3", "jasmine-core": "^3.5.0", "jasmine-jquery": "^2.0.0", "jquery": "^3.4.1", @@ -221,9 +221,9 @@ "dev": true }, "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, "dependencies": { @@ -353,9 +353,9 @@ "dev": true }, "node_modules/axios": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", - "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", "dev": true, "dependencies": { "follow-redirects": "^1.15.0", @@ -448,7 +448,7 @@ "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "engines": { "node": "*" @@ -561,15 +561,15 @@ } }, "node_modules/chromedriver": { - "version": "110.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-110.0.0.tgz", - "integrity": "sha512-Le6q8xrA/3fAt+g8qiN0YjsYxINIhQMC6wj9X3W5L77uN4NspEzklDrqYNwBcEVn7PcAEJ73nLlS7mTyZRspHA==", + "version": "117.0.3", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-117.0.3.tgz", + "integrity": "sha512-c2rk2eGK5zZFBJMdviUlAJfQEBuPNIKfal4+rTFVYAmrWbMPYAqPozB+rIkc1lDP/Ryw44lPiqKglrI01ILhTQ==", "dev": true, "hasInstallScript": true, "dependencies": { "@testim/chrome-version": "^1.1.3", - "axios": "^1.2.1", - "compare-versions": "^5.0.1", + "axios": "^1.4.0", + "compare-versions": "^6.0.0", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0", @@ -579,7 +579,7 @@ "chromedriver": "bin/chromedriver" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/cliui": { @@ -628,9 +628,9 @@ "optional": true }, "node_modules/compare-versions": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", - "integrity": "sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", "dev": true }, "node_modules/concat-map": { @@ -1050,7 +1050,7 @@ "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, "dependencies": { "pend": "~1.2.0" @@ -1467,12 +1467,12 @@ "dev": true }, "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/is-arrayish": { @@ -1566,14 +1566,14 @@ } }, "node_modules/is2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz", - "integrity": "sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", "dev": true, "dependencies": { "deep-is": "^0.1.3", - "ip-regex": "^2.1.0", - "is-url": "^1.2.2" + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" }, "engines": { "node": ">=v0.10.0" @@ -2410,7 +2410,7 @@ "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, "node_modules/picomatch": { @@ -2922,23 +2922,30 @@ } }, "node_modules/tcp-port-used": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", - "integrity": "sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", "dev": true, "dependencies": { - "debug": "4.1.0", - "is2": "2.0.1" + "debug": "4.3.1", + "is2": "^2.0.6" } }, "node_modules/tcp-port-used/node_modules/debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/tcp-port-used/node_modules/ms": { @@ -3272,7 +3279,7 @@ "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, "dependencies": { "buffer-crc32": "~0.2.3", @@ -3459,9 +3466,9 @@ "dev": true }, "@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", "dev": true, "optional": true, "requires": { @@ -3564,9 +3571,9 @@ "dev": true }, "axios": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", - "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", "dev": true, "requires": { "follow-redirects": "^1.15.0", @@ -3645,7 +3652,7 @@ "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true }, "bytes": { @@ -3725,14 +3732,14 @@ } }, "chromedriver": { - "version": "110.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-110.0.0.tgz", - "integrity": "sha512-Le6q8xrA/3fAt+g8qiN0YjsYxINIhQMC6wj9X3W5L77uN4NspEzklDrqYNwBcEVn7PcAEJ73nLlS7mTyZRspHA==", + "version": "117.0.3", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-117.0.3.tgz", + "integrity": "sha512-c2rk2eGK5zZFBJMdviUlAJfQEBuPNIKfal4+rTFVYAmrWbMPYAqPozB+rIkc1lDP/Ryw44lPiqKglrI01ILhTQ==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.3", - "axios": "^1.2.1", - "compare-versions": "^5.0.1", + "axios": "^1.4.0", + "compare-versions": "^6.0.0", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0", @@ -3782,9 +3789,9 @@ "optional": true }, "compare-versions": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", - "integrity": "sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", "dev": true }, "concat-map": { @@ -4109,7 +4116,7 @@ "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, "requires": { "pend": "~1.2.0" @@ -4418,9 +4425,9 @@ "dev": true }, "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", "dev": true }, "is-arrayish": { @@ -4493,14 +4500,14 @@ "dev": true }, "is2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.1.tgz", - "integrity": "sha512-+WaJvnaA7aJySz2q/8sLjMb2Mw14KTplHmSwcSpZ/fWJPkUmqw3YTzSWbPJ7OAwRvdYTWF2Wg+yYJ1AdP5Z8CA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/is2/-/is2-2.0.9.tgz", + "integrity": "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==", "dev": true, "requires": { "deep-is": "^0.1.3", - "ip-regex": "^2.1.0", - "is-url": "^1.2.2" + "ip-regex": "^4.1.0", + "is-url": "^1.2.4" } }, "isbinaryfile": { @@ -5168,7 +5175,7 @@ "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, "picomatch": { @@ -5566,22 +5573,22 @@ } }, "tcp-port-used": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.1.tgz", - "integrity": "sha512-rwi5xJeU6utXoEIiMvVBMc9eJ2/ofzB+7nLOdnZuFTmNCLqRiQh2sMG9MqCxHU/69VC/Fwp5dV9306Qd54ll1Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz", + "integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==", "dev": true, "requires": { - "debug": "4.1.0", - "is2": "2.0.1" + "debug": "4.3.1", + "is2": "^2.0.6" }, "dependencies": { "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -5816,7 +5823,7 @@ "yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, "requires": { "buffer-crc32": "~0.2.3", diff --git a/package.json b/package.json index 9fcde44cf..2be147585 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "devDependencies": { "@metahub/karma-jasmine-jquery": "^2.0.1", - "chromedriver": "^110.0.0", + "chromedriver": "^117.0.3", "jasmine-core": "^3.5.0", "jasmine-jquery": "^2.0.0", "jquery": "^3.4.1", diff --git a/src/integration-test/groovy/au/org/ala/biocollect/AddBioActivitySpec.groovy b/src/integration-test/groovy/au/org/ala/biocollect/AddBioActivitySpec.groovy index 39c3dd8f0..7c7614ba6 100644 --- a/src/integration-test/groovy/au/org/ala/biocollect/AddBioActivitySpec.groovy +++ b/src/integration-test/groovy/au/org/ala/biocollect/AddBioActivitySpec.groovy @@ -1,13 +1,10 @@ package au.org.ala.biocollect - import pages.AddBioActivityPage import pages.ViewBioActivityPage -import spock.lang.Ignore import spock.lang.Stepwise @Stepwise -@Ignore class AddBioActivitySpec extends StubbedCasSpec { def setupSpec() { @@ -15,7 +12,7 @@ class AddBioActivitySpec extends StubbedCasSpec { } def cleanupSpec() { - logout(browser, ViewBioActivityPage) + logout(browser) } def projectId = "project_1" @@ -23,7 +20,7 @@ class AddBioActivitySpec extends StubbedCasSpec { def site = "site_1" def "Add an activity"() { - + setup: loginAsUser('1', browser) when: "go to new activity page" diff --git a/src/integration-test/groovy/au/org/ala/biocollect/StubbedCasSpec.groovy b/src/integration-test/groovy/au/org/ala/biocollect/StubbedCasSpec.groovy index 6771db7b6..d5eee7679 100644 --- a/src/integration-test/groovy/au/org/ala/biocollect/StubbedCasSpec.groovy +++ b/src/integration-test/groovy/au/org/ala/biocollect/StubbedCasSpec.groovy @@ -3,6 +3,11 @@ package au.org.ala.biocollect import com.github.tomakehurst.wiremock.WireMockServer import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer import geb.Browser +import grails.converters.JSON +import org.grails.web.converters.marshaller.json.CollectionMarshaller +import org.grails.web.converters.marshaller.json.MapMarshaller +import org.openqa.selenium.StaleElementReferenceException +import org.pac4j.jwt.profile.JwtGenerator import spock.lang.Shared import wiremock.com.github.jknack.handlebars.EscapingStrategy import wiremock.com.github.jknack.handlebars.Handlebars @@ -12,7 +17,7 @@ import wiremock.com.google.common.collect.ImmutableMap import static com.github.tomakehurst.wiremock.client.WireMock.* import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options - +//import com.github.tomakehurst.wiremock.WireMockServer /** * Supports stubbing access to CAS via wiremock. @@ -26,30 +31,35 @@ class StubbedCasSpec extends BiocollectFunctionalTest { @Shared WireMockServer wireMockServer def setupSpec() { + if (testConfig.wiremock.embedded){ - Handlebars handlebars = new Handlebars() - handlebars.escapingStrategy = EscapingStrategy.NOOP + Handlebars handlebars = new Handlebars() + handlebars.escapingStrategy = EscapingStrategy.NOOP - // This is done so we can use a custom handlebars with a NOOP escaping strategy - the default escapes HTML - // which breaks the redirect URL returned by the PDF generation stub. - Helper noop = new Helper() { - Object apply(Object context, Options options) throws IOException { - return context[0] + // This is done so we can use a custom handlebars with a NOOP escaping strategy - the default escapes HTML + // which breaks the redirect URL returned by the PDF generation stub. + Helper noop = new Helper() { + Object apply(Object context, Options options) throws IOException { + return context[0] + } } - } - wireMockServer = new WireMockServer(options() - .port(testConfig.wiremock.port) - .usingFilesUnderDirectory(getMappingsPath()) - .extensions(new ResponseTemplateTransformer(false, handlebars, ImmutableMap.of("noop", noop), null, null))) + wireMockServer = new WireMockServer(options() + .port(testConfig.wiremock.port) + .usingFilesUnderDirectory(getMappingsPath()) + .extensions(new ResponseTemplateTransformer(false, handlebars, ImmutableMap.of("noop", noop), null, null))) - wireMockServer.start() + wireMockServer.start() + } + JSON.registerObjectMarshaller(new MapMarshaller()) + JSON.registerObjectMarshaller(new CollectionMarshaller()) // Configure the client configureFor("localhost", testConfig.wiremock.port) } def cleanupSpec() { - wireMockServer.stop() + if (testConfig.wiremock.embedded) + wireMockServer.stop() } /** @@ -63,26 +73,45 @@ class StubbedCasSpec extends BiocollectFunctionalTest { } /** Presses the OK button on a displayed bootbox modal */ - def okBootbox() { + def okBootbox(buttonSelector = '.btn-primary') { Thread.sleep(1000) // wait for the animation to finish - $('.bootbox .btn-primary').each { ok -> - - - waitFor 20, { - try { - if (ok.displayed) { - ok.click() - } + // The reason we are doing an "each" and catching exception is sometimes previous dialogs remain + // in scope despite being detached from the DOM and StaleElementException is thrown. + def backdrop = $('.modal-backdrop') + $('.bootbox '+buttonSelector).each { ok -> + try { + if (ok.displayed) { + ok.click() } - catch (Exception e) { - e.printStackTrace() - } + } + catch (Exception e) { + e.printStackTrace() + } + } + Thread.sleep(1000) + // Dismissing bootbox modals is intermittently unreliable, so trying a javascript fallback. + // The other issue here is one of the tests transitions from a page with bootbox up to a page which + // immediately displays a modal, so the Thread.sleep means we can actually be catching the dialog on the + // second page. Hence why we get the element reference at the start. + try { + if (backdrop.displayed) { + js.exec('$(".bootbox ' + buttonSelector + '").click();') + Thread.sleep(1000) waitFor { - $('.modal-backdrop').size() == 0 + boolean backdropDisplayed + try { + backdropDisplayed = backdrop.displayed + } + catch (StaleElementReferenceException e) { + backdropDisplayed = false + // The backdrop was already detached from the DOM due to page transition + } + !backdropDisplayed } } - + } + catch (StaleElementReferenceException e) { // Do nothing, backdrop was already detached } } @@ -114,8 +143,85 @@ class StubbedCasSpec extends BiocollectFunctionalTest { login([userId:userId, email: "user${userId}@nowhere.com", firstName:"MERIT", lastName:"User ${userId}"], browser) } - /** Creates a wiremock configuration to stub a user login request and return the supplied user and role information */ + private String loggedInUser = null + def login(Map userDetails, Browser browser) { + if (loggedInUser != userDetails.userId) { + logout(browser) + } + oidcLogin(userDetails, browser) + loggedInUser = userDetails.userId + } + + def oidcLogin(Map userDetails, Browser browser) { + + // The test config isn't a normal grails config object (probably need to to into why) so getProperty doesn't work. + String clientId = getTestConfig().security.oidc.clientId + List roles = ["ROLE_USER"] + if (userDetails.role) { + roles << userDetails.role + } + + Map idTokenClaims = [ + at_hash:"KX-L2Fj6Z9ow-gOpYfehRA", + sub:userDetails.userId, + email_verified:true, + role:roles, + amr:"DelegatedClientAuthenticationHandler", + iss:"http://localhost:8018/cas/oidc", + preferred_username:userDetails.email, + given_name:userDetails.firstName, + family_name:userDetails.lastName, + client_id:clientId, + sid:"test_sid", + aud:clientId, + name:userDetails.firstName+" "+userDetails.lastName, + state:"maybe_this_matters", + auth_time:-1, + nbf:com.nimbusds.jwt.util.DateUtils.toSecondsSinceEpoch(new Date().minus(365)), + exp:com.nimbusds.jwt.util.DateUtils.toSecondsSinceEpoch(new Date().plus(365)), + iat:com.nimbusds.jwt.util.DateUtils.toSecondsSinceEpoch(new Date()), + jti:"id", + email:userDetails.email + ] + String idToken = new JwtGenerator(null).generate(idTokenClaims) + Map token = [:] + token.access_token = idToken + token.id_token = idToken + token.refresh_token = null + token.token_type = "bearer" + token.expires_in = 86400 + token.scope = "user_defined email openid profile roles" + + stubFor(post(urlPathEqualTo("/cas/oidc/oidcAccessToken")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody((token as JSON).toString()) + .withTransformers("response-template") + )) + + + Map profile = [ + sub:userDetails.userId, + name:userDetails.firstName+" "+userDetails.lastName, + given_name:userDetails.firstName, + family_name:userDetails.lastName, + email:userDetails.email + ] + + stubFor(get(urlPathEqualTo("/cas/oidc/oidcProfile")) + .willReturn(aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody((profile as JSON).toString()) + )) + + browser.go "${getConfig().baseUrl}login" + } + + /** Creates a wiremock configuration to stub a user login request and return the supplied user and role information */ + def casLogin(Map userDetails, Browser browser) { String email = "fc-te@outlook.com" @@ -166,14 +272,14 @@ class StubbedCasSpec extends BiocollectFunctionalTest { .willReturn(aResponse() .withStatus(302) - .withHeader("Location", "{{request.requestLine.query.service}}?ticket=aticket") + .withHeader("Location", "{{{request.requestLine.query.service}}}?ticket=aticket") .withHeader("Set-Cookie", "ALA-Auth=\"${email}\"; Domain=ala.org.au; Path=/; HttpOnly") .withTransformers("response-template"))) stubFor(get(urlMatching("/cas/login\\?service=.*\\?.*")) .willReturn(aResponse() .withStatus(302) - .withHeader("location", "{{request.requestLine.query.service}}&ticket=aticket") + .withHeader("location", "{{{request.requestLine.query.service}}}&ticket=aticket") .withHeader("Set-Cookie", "ALA-Auth=\"${email}\"; Domain=ala.org.au; Path=/; HttpOnly") .withTransformers("response-template"))) diff --git a/src/integration-test/groovy/pages/EntryPage.groovy b/src/integration-test/groovy/pages/EntryPage.groovy index 15099d1f4..a4825bc06 100644 --- a/src/integration-test/groovy/pages/EntryPage.groovy +++ b/src/integration-test/groovy/pages/EntryPage.groovy @@ -4,7 +4,7 @@ import geb.Page class EntryPage extends Page { static url = "" - static at = { title == "Project Finder | BioCollect"} + static at = { title == "Homepage"} static content = { diff --git a/src/integration-test/resources/GebConfig.groovy b/src/integration-test/resources/GebConfig.groovy index 0ad371e34..f17c4eff0 100644 --- a/src/integration-test/resources/GebConfig.groovy +++ b/src/integration-test/resources/GebConfig.groovy @@ -28,7 +28,11 @@ environments { // run as grails -Dgeb.env=chrome test-app chrome { - driver = { new ChromeDriver() } + driver = { + ChromeOptions options = new ChromeOptions() + options.addArguments("--remote-allow-origins=*") + new ChromeDriver(options) + } } firefox { @@ -58,6 +62,7 @@ environments { o.addArguments('headless') o.addArguments("window-size=1920,1080") o.addArguments('--disable-dev-shm-usage') + o.addArguments("--remote-allow-origins=*") new ChromeDriver(o) } } diff --git a/src/integration-test/resources/dataset1/loadDataSet.js b/src/integration-test/resources/dataset1/loadDataSet.js index 9193dd856..3ecc7eb85 100644 --- a/src/integration-test/resources/dataset1/loadDataSet.js +++ b/src/integration-test/resources/dataset1/loadDataSet.js @@ -1,5 +1,5 @@ print("This script is expected to be executed with a working directory containing this script"); -print("Current working dir: "+pwd()); +print("Current working dir: "+process.cwd()); load('../data_common/loadAlaHub.js'); load("../data_common/insertData.js"); loadActivityForms(); diff --git a/src/integration-test/resources/wiremock/mappings/logout.json b/src/integration-test/resources/wiremock/mappings/logout.json index 318368c21..459625288 100644 --- a/src/integration-test/resources/wiremock/mappings/logout.json +++ b/src/integration-test/resources/wiremock/mappings/logout.json @@ -8,7 +8,7 @@ "status": 302, "headers": { "Set-Cookie": "ALA-Auth=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Domain=ala.org.au; Path=/; HttpOnly", - "Location": "{{request.query.url}}" + "Location": "{{{request.requestLine.query.url}}}" } } } diff --git a/src/integration-test/resources/wiremock/mappings/oidcAuthorize.json b/src/integration-test/resources/wiremock/mappings/oidcAuthorize.json new file mode 100644 index 000000000..86c7b7986 --- /dev/null +++ b/src/integration-test/resources/wiremock/mappings/oidcAuthorize.json @@ -0,0 +1,15 @@ +{ + "request": { + "urlPath": "/cas/oidc/oidcAuthorize", + "method": "GET" + }, + "response": { + "status": 302, + "headers": { + "location": "{{{request.requestLine.query.redirect_uri}}}&state={{request.query.state}}&code=12345" + }, + "transformers": [ + "response-template" + ] + } +} \ No newline at end of file diff --git a/src/integration-test/resources/wiremock/mappings/oidcDiscovery.json b/src/integration-test/resources/wiremock/mappings/oidcDiscovery.json new file mode 100644 index 000000000..b5f27fb99 --- /dev/null +++ b/src/integration-test/resources/wiremock/mappings/oidcDiscovery.json @@ -0,0 +1,124 @@ +{ + "request": { + "urlPath": "/cas/oidc/.well-known", + "method": "GET" + }, + "response": { + "status": 200, + "headers": { + "content-Type": "text/json" + }, + "transformers": ["response-template"], + "jsonBody": { + "issuer": "http://{{request.host}}:{{request.port}}/cas/oidc", + "scopes_supported": [ + "openid", + "profile", + "email", + "offline_access", + "ala", + "roles", + "users/read", + "users/write", + "ecodata/read", + "ecodata/write", + "biocache/read", + "biocache/write", + "ala/internal" + ], + "response_types_supported": [ + "code", + "token", + "id_token token" + ], + "subject_types_supported": [ + "public", + "pairwise" + ], + "claim_types_supported": [ + "normal" + ], + "claims_supported": [ + "sub", + "name", + "preferred_username", + "family_name", + "given_name", + "profile", + "locale", + "updated_at", + "email", + "email_verified", + "organisation", + "role", + "authority", + "city", + "state", + "country", + "userid" + ], + "grant_types_supported": [ + "authorization_code", + "password", + "client_credentials", + "refresh_token" + ], + "id_token_signing_alg_values_supported": [ + "none" + ], + "id_token_encryption_alg_values_supported": [ + "none" + ], + "id_token_encryption_enc_values_supported": [ + "none" + ], + "userinfo_signing_alg_values_supported": [ + "none" + ], + "userinfo_encryption_alg_values_supported": [ + "none" + ], + "userinfo_encryption_enc_values_supported": [ + "none" + ], + "request_object_signing_alg_values_supported": [ + "none" + ], + "request_object_encryption_alg_values_supported": [ + "none" + ], + "request_object_encryption_enc_values_supported": [ + "none" + ], + "introspection_endpoint_auth_methods_supported": [ + "client_secret_basic" + ], + "token_endpoint_auth_methods_supported": [ + "client_secret_basic", + "client_secret_post", + "client_secret_jwt", + "private_key_jwt" + ], + "code_challenge_methods_supported": [ + "plain", + "S256" + ], + "claims_parameter_supported": true, + "request_uri_parameter_supported": true, + "request_parameter_supported": true, + "backchannel_logout_supported": true, + "frontchannel_logout_supported": true, + "jwks_uri": "http://{{request.host}}:{{request.port}}/cas/oidc/jwks", + "authorization_endpoint": "http://{{request.host}}:{{request.port}}/cas/oidc/oidcAuthorize", + "userinfo_endpoint": "http://{{request.host}}:{{request.port}}/cas/oidc/oidcProfile", + "pushed_authorization_request_endpoint": "http://{{request.host}}:{{request.port}}/cas/oidc/oidcPushAuthorize", + "registration_endpoint": "http://{{request.host}}:{{request.port}}/cas/oidc/register", + "end_session_endpoint": "http://{{request.host}}:{{request.port}}/cas/oidc/oidcLogout", + "introspection_endpoint": "http://{{request.host}}:{{request.port}}/cas/oidc/introspect", + "revocation_endpoint": "http://{{request.host}}:{{request.port}}/cas/oidc/revoke", + "backchannel_logout_session_supported": true, + "frontchannel_logout_session_supported": true, + "token_endpoint": "http://{{request.host}}:{{request.port}}/cas/oidc/oidcAccessToken" + } + } +} \ No newline at end of file diff --git a/src/main/groovy/au/org/ala/biocollect/merit/userDetails.groovy b/src/main/groovy/au/org/ala/biocollect/merit/userDetails.groovy deleted file mode 100644 index dde3dfddb..000000000 --- a/src/main/groovy/au/org/ala/biocollect/merit/userDetails.groovy +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2013 Atlas of Living Australia - * All Rights Reserved. - * - * The contents of this file are subject to the Mozilla Public - * License Version 1.1 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of - * the License at http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - */ - -package au.org.ala.biocollect.merit - -/** - * Created with IntelliJ IDEA. - * - * @author "Nick dos Remedios " - */ -class UserDetails { - - public static final String REQUEST_USER_DETAILS_KEY = 'ecodata.request.user.details' - - String displayName - String userName - String userId - - public UserDetails(String displayName, String userName, String userId) { - this.displayName = displayName - this.userName = userName - this.userId = userId - } - - public UserDetails() {} - - @Override - public String toString() { - "[ userId: ${userId}, userName: ${userName}, displayName: ${displayName} ]" - } -} \ No newline at end of file diff --git a/src/main/java/au/org/ala/biocollect/IntegrationTestConfiguration.java b/src/main/java/au/org/ala/biocollect/IntegrationTestConfiguration.java new file mode 100644 index 000000000..0209ff7a5 --- /dev/null +++ b/src/main/java/au/org/ala/biocollect/IntegrationTestConfiguration.java @@ -0,0 +1,18 @@ +package au.org.ala.biocollect; + +import au.org.ala.ws.security.AlaWsSecurityConfiguration; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; +import org.pac4j.oidc.config.OidcConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class IntegrationTestConfiguration extends AlaWsSecurityConfiguration { + @Bean + JWKSource jwkSource(OidcConfiguration oidcConfiguration) { + return new ImmutableJWKSet(new JWKSet()); + } +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/scripts/loadFunctionalTestData.sh b/src/main/scripts/loadFunctionalTestData.sh index 9b83fe1f2..d5c10f993 100755 --- a/src/main/scripts/loadFunctionalTestData.sh +++ b/src/main/scripts/loadFunctionalTestData.sh @@ -11,7 +11,7 @@ AUTH_OPTS= if [ "$2" ] then AUTH_OPTS="-u $2 -p $3" - echo "mongo $DATABASE_NAME $AUTH_OPTS --eval " >> /tmp/blah + echo "mongosh $DATABASE_NAME $AUTH_OPTS --eval " >> /tmp/blah fi DATABASE_NAME=ecodata-functional-test @@ -20,7 +20,7 @@ DATA_PATH=$1 cd $DATA_PATH echo $PWD -mongo $DATABASE_NAME $AUTH_OPTS --eval "db.dropDatabase()" -mongo $DATABASE_NAME $AUTH_OPTS loadDataSet.js +mongosh $DATABASE_NAME $AUTH_OPTS --eval "db.dropDatabase()" +mongosh $DATABASE_NAME $AUTH_OPTS loadDataSet.js diff --git a/src/main/scripts/runFunctionalTests.sh b/src/main/scripts/runFunctionalTests.sh index 6c0f69308..141c0380a 100755 --- a/src/main/scripts/runFunctionalTests.sh +++ b/src/main/scripts/runFunctionalTests.sh @@ -1,6 +1,6 @@ #!/bin/bash -v -MERIT_DIR=$PWD +BIOCOLLECT_DIR=$PWD GEB_ENV=$1 if [ -z $GEB_ENV ]; then @@ -22,28 +22,40 @@ if [ ! -d $ECODATA_LOCAL_DIR ]; then git clone https://github.com/AtlasOfLivingAustralia/ecodata.git cd ecodata git checkout $BRANCH + echo "Cloned ecodata $BRANCH into /tmp/ecodata" else cd $ECODATA_LOCAL_DIR + git checkout $BRANCH git pull + echo "Updated ecodata $BRANCH in /tmp/ecodata" fi echo "Dropping database" -mongo ecodata-functional-test --eval 'db.dropDatabase();' -mongo ecodata-functional-test --eval 'db.project.count();' +mongosh ecodata-functional-test --eval 'db.dropDatabase();' +mongosh ecodata-functional-test --eval 'db.project.count();' +cd "$BIOCOLLECT_DIR/src/integration-test/resources/data_common/" +mongosh ecodata-functional-test loadAlaHub.js + +echo "Hosts file configuration" +cat /etc/hosts +cd $ECODATA_LOCAL_DIR echo "Starting ecodata from `pwd`" ls -la -GRADLE_OPTS="-Xmx512m" ./gradlew bootRun --no-daemon "-Dorg.gradle.jvmargs=-Xmx512m" -Dgrails.env=meritfunctionaltest & -sleep 120 +GRADLE_OPTS="-Xmx1g" ./gradlew bootRun "-Dorg.gradle.jvmargs=-Xmx1g" -Dgrails.env=meritfunctionaltest & +sleep 240 -cd $MERIT_DIR -GRADLE_OPTS="-Xmx512m" ./gradlew bootRun --no-daemon "-Dorg.gradle.jvmargs=-Xmx512m" -Dgrails.env=test -Dgrails.server.port.http=8087 & -sleep 180 +cd $BIOCOLLECT_DIR +echo "Starting wire mock" +./gradlew startWireMock +echo "Starting biocollect" +GRADLE_OPTS="-Xmx1g" ./gradlew bootRun "-Dorg.gradle.jvmargs=-Xmx1g" -Dgrails.env=test -Dgrails.server.port.http=8087 & +sleep 200 chmod u+x src/main/scripts/loadFunctionalTestData.sh echo "Running functional tests" -./gradlew integrationTest --stacktrace -Dgeb.env=$GEB_ENV +GRADLE_OPTS="-Xmx1g" ./gradlew integrationTest "-Dorg.gradle.jvmargs=-Xmx1g" --stacktrace -Dgeb.env=$GEB_ENV RETURN_VALUE=$? diff --git a/src/test/groovy/au/org/ala/biocollect/merit/ProjectControllerSpec.groovy b/src/test/groovy/au/org/ala/biocollect/merit/ProjectControllerSpec.groovy index 0ea908e1c..df867f6b9 100644 --- a/src/test/groovy/au/org/ala/biocollect/merit/ProjectControllerSpec.groovy +++ b/src/test/groovy/au/org/ala/biocollect/merit/ProjectControllerSpec.groovy @@ -5,6 +5,7 @@ import au.org.ala.biocollect.ProjectActivityService import au.org.ala.biocollect.VocabService import au.org.ala.biocollect.merit.hub.HubSettings import au.org.ala.web.AuthService +import au.org.ala.web.UserDetails import grails.testing.web.controllers.ControllerUnitTest import org.apache.http.HttpStatus import spock.lang.Specification @@ -59,7 +60,7 @@ class ProjectControllerSpec extends Specification implements ControllerUnitTest< userServiceStub.getOrganisationIdsForUserId(_) >> [] userServiceStub.isProjectStarredByUser(_, _) >> [isProjectStarredByUser:true] roleServiceStub.getRoles() >> [] - authServiceStub.getUserId() >> '' + userServiceStub.getCurrentUserId() >> '' blogServiceStub.get(_, _) >> [] organisationStub.get(_) >> [organisationId: "ABC123", name: "organisation name"] vocabServiceStub.getVocabValues() >> [] @@ -70,7 +71,7 @@ class ProjectControllerSpec extends Specification implements ControllerUnitTest< void "creating a citizen science project should pre-populate the citizen science project type"() { when: - userServiceStub.getUser() >> [userId:'1234'] + userServiceStub.getUser() >> new UserDetails(1, '', '', '', '', '1234', false, true, null) params.citizenScience = true def model = controller.create() @@ -82,7 +83,7 @@ class ProjectControllerSpec extends Specification implements ControllerUnitTest< void "creating a project should pre-populate the organisation if the user is a member of exactly one organisation"() { when: - userServiceStub.getUser() >> [userId:'1234'] + userServiceStub.getUser() >> new UserDetails(1, '', '', '', '', '1234', false, true, null) def model = controller.create() @@ -93,7 +94,7 @@ class ProjectControllerSpec extends Specification implements ControllerUnitTest< void "when creating a project, the current hub's default program should be assigned to the new project"() { when: - userServiceStub.getUser() >> [userId:'1234'] + userServiceStub.getUser() >> new UserDetails(1, '', '', '', '', '1234', false, true, null) SettingService.setHubConfig(new HubSettings([defaultProgram:'my program'])) def model = controller.create() @@ -107,7 +108,7 @@ class ProjectControllerSpec extends Specification implements ControllerUnitTest< when: def projectId = 'project1' - userServiceStub.getUser() >> [userId:'1234'] + userServiceStub.getUser() >> new UserDetails(1, '', '', '', '', '1234', false, true, null) projectServiceStub.get(projectId, _) >> [organisationId:'org1', projectId:projectId, name:'Test', projectSiteId:siteId] params.organisationId = 'org2' @@ -396,7 +397,7 @@ class ProjectControllerSpec extends Specification implements ControllerUnitTest< void "get my Citizen Science Projects"() { setup: - userServiceStub.getUser() >> [userId:'1234', userName:"test", displayName:"test"] + userServiceStub.getUser() >> new UserDetails(1, 'test', 'test', 'test', 'test', '1234', false, true, null) SettingService.setHubConfig(new HubSettings([defaultProgram:'my program', defaultFacetQuery:"isCitizenScience:true"])) when: @@ -407,7 +408,7 @@ class ProjectControllerSpec extends Specification implements ControllerUnitTest< view == "/project/projectFinder" model.user.userId == "1234" model.user.userName == "test" - model.user.displayName == "test" + model.user.displayName == "test test" model.showTag == true model.downloadLink == "/ws/project/search?initiator=biocollect&download=true" model.isUserPage == true diff --git a/src/test/groovy/au/org/ala/biocollect/merit/SiteControllerSpec.groovy b/src/test/groovy/au/org/ala/biocollect/merit/SiteControllerSpec.groovy index ef6c4e97c..6c807d363 100644 --- a/src/test/groovy/au/org/ala/biocollect/merit/SiteControllerSpec.groovy +++ b/src/test/groovy/au/org/ala/biocollect/merit/SiteControllerSpec.groovy @@ -1,22 +1,22 @@ package au.org.ala.biocollect.merit -import au.org.ala.web.AuthService + import grails.testing.web.controllers.ControllerUnitTest -import org.apache.http.HttpStatus import grails.web.servlet.mvc.GrailsParameterMap +import org.apache.http.HttpStatus import spock.lang.Specification class SiteControllerSpec extends Specification implements ControllerUnitTest { SiteService siteService = Stub(SiteService) - AuthService authService = Stub(AuthService) CommonService commonService = Stub(CommonService) + UserService userService = Stub(UserService) def setup() { controller.siteService = siteService - controller.authService = authService controller.commonService = commonService - authService.getUserId() >> '1' + controller.userService = userService + userService.getCurrentUserId() >> '1' } def cleanup() {