From 97aa278ebb9d56c1a9120014d1d2a9a302e36142 Mon Sep 17 00:00:00 2001 From: Sushant G <105408469+sughics@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:29:32 +1100 Subject: [PATCH 01/31] Bumping SNAPSHOT version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5d3175bb..0318c424 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ plugins { } -version "3.1.2-SNAPSHOT" +version "3.1.3-SNAPSHOT" group "au.org.ala" From 06a770e6000c27d487e0a28ebafd51e4da9e8a0a Mon Sep 17 00:00:00 2001 From: vjrj Date: Fri, 10 Feb 2023 09:30:40 +0100 Subject: [PATCH 02/31] Fix for #179 --- grails-app/migrations/changelog.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/grails-app/migrations/changelog.xml b/grails-app/migrations/changelog.xml index 230a3557..bde34120 100644 --- a/grails-app/migrations/changelog.xml +++ b/grails-app/migrations/changelog.xml @@ -408,4 +408,20 @@ liquibase.command.referencePassword: XXXXX + + + SELECT COUNT(1) FROM sequence; + + + INSERT INTO `sequence` (id, version, name, next_id, prefix) + VALUES (1,0,'collection',0,'co'), + (2,0,'institution',0,'in'), + (3,0,'dataProvider',0,'dp'), + (4,0,'dataResource',0,'dr'), + (5,0,'dataHub',0,'dh'), + (6,0,'attribution',0,'at'), + (7,0,'tempDataResource',0,'drt'); + + + From 33a844a79b449d79390b8b45be40bfe36027056a Mon Sep 17 00:00:00 2001 From: vjrj Date: Tue, 21 Feb 2023 20:44:01 +0100 Subject: [PATCH 03/31] Removed sequence.version as is not in the model --- grails-app/migrations/changelog.xml | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/grails-app/migrations/changelog.xml b/grails-app/migrations/changelog.xml index bde34120..c0527e6e 100644 --- a/grails-app/migrations/changelog.xml +++ b/grails-app/migrations/changelog.xml @@ -408,19 +408,27 @@ liquibase.command.referencePassword: XXXXX + + + + + + + + SELECT COUNT(1) FROM sequence; - INSERT INTO `sequence` (id, version, name, next_id, prefix) - VALUES (1,0,'collection',0,'co'), - (2,0,'institution',0,'in'), - (3,0,'dataProvider',0,'dp'), - (4,0,'dataResource',0,'dr'), - (5,0,'dataHub',0,'dh'), - (6,0,'attribution',0,'at'), - (7,0,'tempDataResource',0,'drt'); + INSERT INTO `sequence` (id, name, next_id, prefix) + VALUES (1,'collection',0,'co'), + (2,'institution',0,'in'), + (3,'dataProvider',0,'dp'), + (4,'dataResource',0,'dr'), + (5,'dataHub',0,'dh'), + (6,'attribution',0,'at'), + (7,'tempDataResource',0,'drt'); From 8c99165377d9498afc38ce34ebd49e013d2d1546 Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Wed, 1 Mar 2023 17:05:18 +1100 Subject: [PATCH 04/31] issue #181 initial grails5 and gradle7 upgrade --- .travis.yml | 1 + build.gradle | 186 +++++------- gradle.properties | 10 +- gradle/wrapper/gradle-wrapper.jar | Bin 55190 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 282 +++++++++++------- gradlew.bat | 173 +++++------ grails-app/conf/application.yml | 5 + .../{logback.groovy => logback.old.groovy} | 0 grails-app/conf/logback.xml | 17 ++ 10 files changed, 374 insertions(+), 302 deletions(-) rename grails-app/conf/{logback.groovy => logback.old.groovy} (100%) create mode 100644 grails-app/conf/logback.xml diff --git a/.travis.yml b/.travis.yml index 65508237..d47c12be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ branches: - master - develop - feature/issue-175 + - feature/grails5 before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock diff --git a/build.gradle b/build.gradle index 0318c424..de23695a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,15 +6,15 @@ buildscript { } dependencies { classpath "org.grails:grails-gradle-plugin:$grailsVersion" - classpath "org.grails.plugins:hibernate5:7.0.5" - classpath "gradle.plugin.com.github.erdi.webdriver-binaries:webdriver-binaries-gradle-plugin:2.0" - classpath "com.bertramlabs.plugins:asset-pipeline-gradle:3.3.2" - classpath 'org.grails.plugins:database-migration:3.1.0' + classpath "org.grails.plugins:hibernate5:${gormVersion}" + classpath "gradle.plugin.com.github.erdi.webdriver-binaries:webdriver-binaries-gradle-plugin:$webdriverBinariesVersion" + classpath "com.bertramlabs.plugins:asset-pipeline-gradle:3.4.6" + classpath 'org.grails.plugins:database-migration:4.0.0' } } plugins { - id "com.gorylenko.gradle-git-properties" version "2.4.0-rc2" + id "com.gorylenko.gradle-git-properties" version "2.4.1" } @@ -31,12 +31,9 @@ apply plugin:"com.bertramlabs.asset-pipeline" apply plugin:"org.grails.grails-gsp" apply plugin:"maven-publish" -bootJar { - enabled = true - classifier = 'exec' -} publishing { + targetCompatibility = 1.11 repositories { maven { name 'Nexus' @@ -49,34 +46,15 @@ publishing { } publications { mavenJar(MavenPublication) { - pom.withXml { - def pomNode = asNode() - pomNode.dependencyManagement.replaceNode {} - - // simply remove dependencies without a version - // version-less dependencies are handled with dependencyManagement - // see https://github.com/spring-gradle-plugins/dependency-management-plugin/issues/8 for more complete solutions - pomNode.dependencies.dependency.findAll { - it.version.text().isEmpty() - }.each { - it.replaceNode {} - } - } - from components.web -// if (bootJar && bootJar.enabled && bootJar.classifier) { -// def repackagedFile = file("$libsDir/$project.name-$project.version-${bootJar.classifier}.jar") -// if (repackagedFile.exists()) { -// artifact(repackagedFile) { -// classifier bootRepackage.classifier -// } -// } else { -// logger.quiet("Spring Boot repackage with classifier specified but file is not present!") -// } -// } + artifact bootWar } } } +bootWar { + launchScript() +} + repositories { mavenLocal() maven { url "https://nexus.ala.org.au/content/groups/public/" } @@ -100,92 +78,87 @@ sourceSets { dependencies { developmentOnly("org.springframework.boot:spring-boot-devtools") - compile "org.springframework.boot:spring-boot-starter-logging" - compile "org.springframework.boot:spring-boot-autoconfigure" - compile "org.grails:grails-core" - compile "org.springframework.boot:spring-boot-starter-actuator" - compile "org.springframework.boot:spring-boot-starter-tomcat" - compile "org.grails:grails-web-boot" - compile "org.grails:grails-logging" - compile "org.grails:grails-plugin-rest" - compile "org.grails:grails-plugin-databinding" - compile "org.grails:grails-plugin-i18n" - compile "org.grails:grails-plugin-services" - compile "org.grails:grails-plugin-url-mappings" - compile "org.grails:grails-plugin-interceptors" - compile "org.grails.plugins:cache" - compile "org.grails.plugins:async" - compile "org.grails.plugins:scaffolding" - compile "org.grails.plugins:events" - compile "org.grails.plugins:hibernate5" - compile "org.hibernate:hibernate-core:5.4.18.Final" - compile "org.grails.plugins:gsp" - - compile "com.opencsv:opencsv:3.7" - compile "io.micronaut:micronaut-http-client" + implementation "org.springframework.boot:spring-boot-starter-logging" + implementation "org.springframework.boot:spring-boot-autoconfigure" + implementation "org.grails:grails-core" + implementation "org.springframework.boot:spring-boot-starter-actuator" + implementation "org.springframework.boot:spring-boot-starter-tomcat" + implementation "org.grails:grails-web-boot" + implementation "org.grails:grails-logging" + implementation "org.grails:grails-plugin-rest" + implementation "org.grails:grails-plugin-databinding" + implementation "org.grails:grails-plugin-i18n" + implementation "org.grails:grails-plugin-services" + implementation "org.grails:grails-plugin-url-mappings" + implementation "org.grails:grails-plugin-interceptors" + implementation "org.grails.plugins:cache" + implementation "org.grails.plugins:async" + implementation "org.grails.plugins:scaffolding" + implementation "org.grails.plugins:events" + implementation "org.grails.plugins:hibernate5" + implementation "org.hibernate:hibernate-core:5.4.18.Final" + implementation "org.grails.plugins:gsp" + + implementation "com.opencsv:opencsv:3.7" + implementation "io.micronaut:micronaut-http-client" compileOnly "io.micronaut:micronaut-inject-groovy" - compile "org.apache.httpcomponents:httpclient:4.5.13" - compile "commons-io:commons-io:2.11.0" + implementation "org.apache.httpcomponents:httpclient:4.5.13" + implementation "commons-io:commons-io:2.11.0" // See: https://github.com/AtlasOfLivingAustralia/collectory/issues/84#issuecomment-1070670979 // before updating mysql-connector-java - compile "mysql:mysql-connector-java:8.0.22" - compile "org.grails.plugins:ala-bootstrap3:4.1.0" - compile "au.org.ala.plugins.grails:ala-charts-plugin:2.1.2" - compile "org.grails.plugins:ala-auth:5.1.1" - compile "org.grails.plugins:ala-ws-security-plugin:4.1.2" - compile "org.grails.plugins:ala-ws-plugin:3.1.1" - compile "org.grails.plugins:audit-logging:4.0.3" - compile "org.grails.plugins:external-config:2.0.0" - compile "org.grails.plugins:ala-admin-plugin:2.3.0" - compile 'au.org.ala.plugins:openapi:1.1.0' + implementation "mysql:mysql-connector-java:8.0.22" + implementation "org.grails.plugins:ala-bootstrap3:4.1.0" + implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.1.2" + implementation "org.grails.plugins:ala-auth:5.2.0-SNAPSHOT" + implementation "org.grails.plugins:ala-ws-security-plugin:4.4.0-SNAPSHOT" + implementation "org.grails.plugins:ala-ws-plugin:3.1.2" + implementation "org.grails.plugins:audit-logging:4.0.3" + implementation 'dk.glasius:external-config:3.1.1' + implementation "org.grails.plugins:ala-admin-plugin:2.3.0" + implementation 'au.org.ala.plugins:openapi:1.1.0' console "org.grails:grails-console" profile "org.grails.profiles:web" - runtime "org.glassfish.web:el-impl:2.1.2-b03" - runtime "com.h2database:h2" - runtime "org.apache.tomcat:tomcat-jdbc" - runtime "javax.xml.bind:jaxb-api:2.3.1" - runtime "com.bertramlabs.plugins:asset-pipeline-grails:3.2.4" - testCompile "io.micronaut:micronaut-inject-groovy" - testCompile "org.grails:grails-gorm-testing-support" - testCompile "org.mockito:mockito-core" - testCompile "org.grails:grails-web-testing-support" - testCompile "org.grails.plugins:geb" - testCompile "org.seleniumhq.selenium:selenium-remote-driver:3.141.59" - testCompile "org.seleniumhq.selenium:selenium-api:3.141.59" - testCompile "org.seleniumhq.selenium:selenium-support:3.141.59" - testRuntime "org.seleniumhq.selenium:selenium-chrome-driver:3.141.59" - testRuntime "org.seleniumhq.selenium:selenium-firefox-driver:3.141.59" - compile 'org.grails.plugins:sentry:11.7.25' - // In newer versions use 'implementation' - compile 'org.liquibase:liquibase-core:3.10.3' - compile "org.grails.plugins:database-migration:3.1.0" + runtimeOnly "org.glassfish.web:el-impl:2.1.2-b03" + runtimeOnly "com.h2database:h2" + runtimeOnly "org.apache.tomcat:tomcat-jdbc" + runtimeOnly "javax.xml.bind:jaxb-api:2.3.1" + runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:3.4.6" + testImplementation "io.micronaut:micronaut-inject-groovy" + testImplementation "org.grails:grails-gorm-testing-support" + testImplementation "org.mockito:mockito-core" + testImplementation "org.grails:grails-web-testing-support" + testImplementation "org.grails.plugins:geb" + testImplementation "org.seleniumhq.selenium:selenium-remote-driver:$seleniumVersion" + testImplementation "org.seleniumhq.selenium:selenium-api:$seleniumVersion" + testImplementation "org.seleniumhq.selenium:selenium-support:$seleniumVersion" + runtimeOnly "net.sourceforge.htmlunit:htmlunit:2.18" + testImplementation "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1" + testImplementation "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion" + testImplementation "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion" + testImplementation "org.seleniumhq.selenium:selenium-safari-driver:$seleniumSafariDriverVersion" + implementation 'org.grails.plugins:sentry:11.7.25' + // db-migration + implementation 'org.liquibase:liquibase-core:4.19.0' + implementation('org.grails.plugins:database-migration:4.2.0') { + // spring-boot-cli exclusion required since Grails5 upgrade to prevent NullPointerException Error: https://github.com/grails/grails-database-migration/issues/268 + exclude module: 'spring-boot-cli' + } } bootRun { ignoreExitValue true jvmArgs( - '-Dspring.output.ansi.enabled=always', - '-noverify', - '-XX:TieredStopAtLevel=1', - '-Xmx1024m') + '-Dspring.output.ansi.enabled=always', + '-noverify', + '-XX:TieredStopAtLevel=1', + '-Xmx2048m') sourceResources sourceSets.main String springProfilesActive = 'spring.profiles.active' systemProperty springProfilesActive, System.getProperty(springProfilesActive) } -tasks.withType(GroovyCompile) { - configure(groovyOptions) { - forkOptions.jvmArgs = ['-Xmx1024m'] - } -} - -webdriverBinaries { - chromedriver '2.45.0' - geckodriver '0.24.0' -} - tasks.withType(Test) { systemProperty "geb.env", System.getProperty('geb.env') systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest") @@ -193,14 +166,17 @@ tasks.withType(Test) { systemProperty "webdriver.gecko.driver", System.getProperty('webdriver.gecko.driver') } +webdriverBinaries { + chromedriver "$chromeDriverVersion" + geckodriver "$geckodriverVersion" +} assets { minifyJs = true minifyCss = true } -springBoot { - buildInfo() +test { + testLogging.showStandardStreams = true } - diff --git a/gradle.properties b/gradle.properties index 903ab8ca..aed1a7e7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,11 @@ -grailsVersion=4.1.1 -gorm.version=7.0.8.RELEASE +grailsVersion=5.2.1 +gormVersion=7.2.1 org.gradle.daemon=true org.gradle.parallel=true org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024M +gebVersion=2.3 +seleniumVersion=3.14.0 +webdriverBinariesVersion=2.6 +chromeDriverVersion=2.45.0 +geckodriverVersion=0.24.0 +seleniumSafariDriverVersion=3.14.0 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738cbd051603d91cc39de6cb000dd98fe6b02..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch delta 26684 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 K^lt7S+W!H~Tue9s delta 22520 zcmZ6SQ*fXSu%?5FZ6^~unb@{%+cv+6Z6}jVY}*stwrv~x@2PXMTh;e{(N&G-?KgfF zJhcYAmKGGDeARpf90CO73T z#c%bTF)D}}SV6ax!Sa5w{(R~8bZ~9)PXNLZR)8veFE9{PcFa>|NDQK@wQz^Inkv0d z4XBf)g(faAglsTwTM9TAzH^5^J1Cd0xhlSh{lZdUXi)RSkis;wLB#&R2n}o`L zKa^i7K6$M>zk++rEzqV!S7C9&?01I@Za(zs7Vk=8r%~A`4AhnoH&deK4ZgyA02-HI zA_hT${Ti}49`j!~R!{6`&$amRST=4x_LhMU9w-{G9W36fDoJ(1y$t*#_w_a_>SE#v zYR;)m74kW7TuL+5($q#3d(g4voMFp2Pj>xi zL2I7*eLVy?9%|`SqqV9lR246R0G-6nNNK5~lA06JGqu@SMzTSEves~7`$bojt}1Vj z$e7NJY`9LT@+~pH@yQmtfljwbW5o;hU&TSNhooY?-9QP9zk8PP4%wJzUh|rmZGOdj zF1~qtGQMJ#pM~N5Z=<$n zOM1t$TbK?jOJeT?G>M%FKn^J(Vlz?0txE~(epnIfh~7Dz+4rWL;2YABWH%&cum`cr zEw{FOJa{{>WYh#ZXrYx}Vv_!bjSi9(P(k9Ju6p)`Oe}lyq z4a`}n!X}|-HU}@HOo~Q|sWckCKnr%BBZ_yi-iIZkgxBxhKxWnmCo z#C4Ae+21{}pSv0ZLh?6Pai5~a_E5wFEg4v4uYDW_ivA({&`tP@AO%Uv-zD+{$ZNk- zy@2*PA@@Jb3dH#9_8HL$_S^Mo=b|NiUEUVg0XL5+?B? z;sPpq3TQ&;d=2!rdd->|c47x2f*bknQ3kUM>G8OzN!F5zsQvRZ@@s74@+{ffZ*j6S zq6Wd1dq_7DIM>O62&`hyMixg~E>nCS_D9Rt*8-p$!zNH@>25p5OGCP__lWk!#36&| zsS{7BL1r87@xL8R28RejZ!L`Q&yL|?1b}~o@pR*vhOL?~Cb#Wf9i$6*-DpY!6(clp z&8@JLZA7wX;w$+Zp@}}5P0jYLv@OW;Vs&MzXl^!A9Jfc=~0CwZp~Fn zcS0@(5)PV*8o&BrGf0#38O6YzjPW4mi2_>-AJc_Nz9kpSdz0S)51A zJ!Dbr>Ux=1;3@}sF%s}0-yO1{7ZG(T5g7?`ET-NA;lq!H4eN8pX-0h$Vp76q4E^;6 zIKM41h8~r_Y{Pd)k#_`?SxQVyg@JMBpbEAk&J4-7*Gpy*d&!8)`iz^0aCRnznxT+a z)Y^%kLX5xVL91B7qpR?H-lXy1aNgpc1Bskq%=!61cy)`|BIum`?r0-3b+4dMuR9Nj zhdQ06`9;t081Q>uok@;S5LPHx6$cnLLh9-yjeYnARvDTk`PfT zfov@pU)^D?Z(#1KyJT^G_pzi%pJh*^$a<))#U66XG@o_9>jpE0dV1Tnq?je=pl;RbprS`KxLzsI}mE^er ztHV*2!%@dKOT+6%i~bw@@9ev!pG*78K;TKQMa4=-Ugomu{dxH-0^2(m+CQ8Dhuszg zU;gl){*>X{gVzwZ@cssDK(6PPA{&4{fcQ}tz=-{l^fPr|AqaYRI?$8QKy}_gdGc@8 zOVrP;xPPjX1~@PEZ(nZ6)4L9VFFL+rY=DNC72vQ(6!;?XkrV*1-+Ciw-7@+ErNHCw zh#JV-nwJ*q51ud5aEsf&6kjq*Upki$Y+|pO0X4LKUlMHJ6`9{ElN_q|q1N4HnGV$1 z_L;&BZ(_1#zl!FN4Upe&wNySnq<_;Hm*z-uNU0&~p|TcnhuauuD4R%Dkt}8?JB%@d zD`2C-jyY=?jY&71(}<|!)ej{DwbV8US{7q(KSND}sPW|5#4hpjDC$R(b>!M6lP8O9 znGW4mnltBe*;mXZ3y9^wDrgfNH>O&%rMakuN~h)YbGYK4D6)gG@S3^`X-n}gxVGkf z%8xf@^1duJ9FSJqr`NGAn_IKfEu*~c>6q#i6z4N!3we2V5ms*-O{5!u+~%5p&ywGH zmJhneO7#86OK!a7FC@{Aic7~1_%WHS$M-B};1?viZPo?PC*|NeZGifaT~QsAOI*IhUk)vB zN<4TaF}(r($}}p22Z@<}97o++EK6||X!~^$jpuZ-2$xS|kb|mdj~BCWlozLe*p4*i zD|b>uFS=odZSXFx$D|_2*Aa*JVp9!QOiZNNie;#28%!spyy`>&d3C!}ZHk_}@q($w z%!9qR&WY;a<5n0R`NKJT-`?s?UG*$^uB!kT%Cs{04@qxdT{GFi48*e}Nrguq>1W(E zc?YjedE>q;Q@VSur3_hG;)!FE0}^0s5>ex|HFB+*8fAPz^9)k&BFwjcBl%7z)oimoKAhrv&ie*rYY_SCX>Nrax>NtzW-sx)hfylHUq>~R9 zt;eIKK1kM(`$?};9+H`$syOZzm+)Ck#QV$SGyl}oFTL3k{(|Xyr6*0hP%X4Hs!&cH zSN%lNW+p3v_lhF#cV0_maaISBxk>|-AyhP~jYOMHbV+YG5L#T3o<>>3Q=7!byE@k# zF;oY3G-hSAm>JLlX-g0vo?RdDBjAhEEfWgB!TnHTa1)eQ!pRu9?FZx)*OOVs3P_ma z;87THMoC?Qqevtg!AVVc!#7qWwQn26-}VrhMCfG(HI))vg$x^0v`PoaH&^<(;uS+k)m{tHqjlEGD(tCG3C?KQ!+Q~sCagfk8pfE?!5a))C4*m=Bll|Y;l`1WCgnA9(X13a@^+fCS#7I_Ksmu9q zJK8htyINjhI%<_&Jt#Pj3}-lOT|K znCB#V{ge|8(3^GA>??ULRWi0q8>l=#yARv^2x0u?W<+;X{LpmI3O6ca4FKqz$Jd_Y zxR?g4{RoE+y4KaHM--9Zb?&`6|o zX4j(GMJ@}5jOSIlzX<&uZZ>v!K&DOkD2 zi2(G5hWTWMm`$XI)%1=p=jvtQaC;$u7bbq%x@BuVNPie$p3&#Ajyn75<6YpC323I*Ti0lk^SoSdZ*+ zmP4@3S%!P+ohe;Ibfn!#4)X%%YF0w|KGLcoSJXx++5-h#D8ThW^c%6G$vzzoZdP?( zecd6hZ;a74+Y4S+4RH^QiT;&1E`n`wBmKs;`+_m8dTv1MN+`mD@M%45hlx;H41^xL zH4Cll^n811&fJ&;4UrO8m3LEWPh`9off|P*PuNcFME)B$U6D-DQjvO%LmrZL9sYWg zEo}z_<@;r(29TQc0`3VVX4<7)hPA|Ip_n9eSvFLBq_r@)p1%2vc-0a0$@AEqaFG}& z=hhlXv`~L`BeE7HFwq^CO!a;}23N5W{QGOQCKRc_9hi4pu&Ax%`xQ2GgP)3|aucal zc>x2(thsn)V+;FkUYE&WFmE61m*==LVraLL*4ys>4b(&Wj@fc7TJ~QVF6Nw-^RXEL zD-(wMH&^A6$4qVnEvCp`u(Sf9a(Ox|t#*i6VIn%_PVeIt*6Qkdb3z5M3pZaipn=$raUOPM{MGNE zHSB78`bS&Z!uw1#_(}xglxsa-I0L>oQS8jF1k=DQc5K~SEK7EK#ugvE3}epE;d#yo zqX(3MKrQ)={e#m-Xw3JAK)_#7Hn+~`=UNRB z8~uKTS@plt(Z14kX)@GL_0(#_i=L?0B1NT*Q`I+75ha#M29(bgrR}3JHt>}kwf#LT z0Avo^jX(G_j^~!(zW_93qQec-7=}Ue%yYbxRw+5~C>$6i4lB7)cyv8@-yI6LYX4c; zFCv_kjY2-NXL=fZmCx3Min;xVe3}|LmldXs^j>)*7pQOd+iUr+%cJcatqr#(#TL-6 z9bpti+rH_!qI(j4$tk4WelC=M=|Rh%K#W|&%KH2rt}kQgS{4;;ke@>ToRlv|QY)A1 zH2xFP^xoEt9}d1H8M?b z{M9%tY(s7skKJQd_HI2B_d&c;^JsPS=oMLt?&}6cYA09($0MiqeM87FdDbvQz`~Kt zwefELWNb%M!R4eC*W;a&uo^x)JaOg924(8mbY}eu>AlZi{s~@#J>-pzV+Kf} zg<2Tvol7zLo4jxTJZWUf=_(DnqN86NNfEU}7PO`yLv+Y-ULfub*|=&NS!0ygGFRC( zFrGwL;g7jMCUdHUId4Koc39H@JoMl?e}Ij*ByO}YSrZlEJte-$yqJkQCh||s5t=O2 z?1??S!JrbEC>TBMLl_8De~!^Y9|HJ)b7RN)v$t-Eo zOn+tZ6y_XBLbym+riU#=%vd$^O>#KGCD`jR7+0o$cKy}EgN1YQWuNOV@2(@~XMknA-VE&H z>N%<9zWcfBd->mU4ica9A@1)g<)Uax>Xj%;T1Ce=>>QHNk}=QBKvW@y-gz@#ks+W< zpLagW0;aD4gBNQ0anDQtPui_7MAVOcl1@RoZpGdx$G_2Vu&LY!ShSun`ZR_H6rpKR zyBM$xd;T~z4|{6*o!SAx%Ojpufoz`Hf!3Y+LwM+wXdqa(;y4a>dy}wWo>75NH@Pp~ z71&!8p)EeQh>Tlhx#-uZ_a3yZ!Q%kzR?stzC(%~T&xqt`DI6ljS~v}KS;Mg?q(}sf zu2zcu_Lq-%&5v5mk8r`=WrHM#0kvi#k{?QDrnpv`+?APyq8MImyD7EF~i&ygI&*L z9)cr)mC*cWx6;qFCi=K{PAt|Bh8Eu{Jr9nDpwgda-AL5RLTUgT2L%}0I=~Fc?HcjJ zu##R6CY08Md|ht8MLKYXX=#xQb{)pY`D^CbxV!7iA*o3)f*^S%JD~1F#wD$BT(^7} z=AxvXXN!{(0LJbRtVQiQZYnCWlD|^B1uHM6YDUNn1dtvYHN6#pWN2KQ;`&s_N&<#-Tzti~tCMxHQaG}W$ zFT?KLH~WwrjJrjHq1Bx@L+@foH9F*m+gJ69ay(IR;p3bHBpbxD_MP~~Dw!a*rw49a zV44^v-dDo+Fe&>CWI2a(4R+E{7cO}7rKYAPS-NCqQC{W6* zzOZ~wScnd5*IeaZz&Zvd9JaXz=YvjW(N-Xw5Gw+E|FdqMX-WOrmC*pNG;11E*|<>1 zqn*&DLC{s9rARBXnFm3iy?iihGC@=|PRpe%wHL!;ee_1M&$POV)>12ED7;(G_HWL8 z)6k>mIIw2E%>8~9Zg1A|bU0ttb<>kHe_R*rcW+s*MP&;u=jP=C6SX-I&LiAsjfv2M zH4>JKv^3$TJO_F7EcjJ+xbXNCxNom7;ZWV{B_VF~YvWoq>dq|5{kgKo{?q9#-brur zG_)B+tEDRURK(tDpQg{Mx7x;@U?J5d>b!|`8-RFZh<`1Shhm{3sa8^xoou`22#GK& zX*93L&rVM>Kay=feVFQ+9WyoDASnq2)y3tbp&D|i~c^KC`G$m1J z2pp}YwV`uQ@~kAUPXyT-rW$cP^2$4j>E>Kkb}P+eV=W7=^N{#<e~k z8t5Y%vlzh$)TJH)SJ52E?jAA*>Q;0PpBE_WTI)B@+JQ%Ufp8iA`D0i?|)Xxt|?{VRdPMzpa>`6f^lwP??V99Lp!Q~T<= zhJUJ!{$92-4f7okHJWe?4v!#jYDJP=9V~-Mw@z4$B1cjk3vI-h9B@Zt&$6|Da2``4 z8*{o|;U7_{EM0c4A~3}s*TrDHD&_0t+8g~b=9FJ|CKG*`%<|_RBQ~br0UYQFxI%gi zyJ{Fh-lS}Ek>(2#v;5!6-`S{m2I>D_V-6=7BkJXXc2_xw^YTS>Fk&7!x( zkGG3D54ht~bj_L#1jAWy!jD+DnRRx_*l!&gYIRXG(aJesQ8JQ3^4 zt)Jf7X-DW!di7#zzf9ccq=XZ!BSFRs10nWI>Qw7g)tR3_#lO{Q$j~c^9U#8bP*GM%HNp&1 z)ERh|P|*esQ=M?C|H?4$RJAKfC9Q>?gN@v6_(gB7p)LE0z80Y`q(tp{!1HN}sq2L+ z--l*fP5FhC1kd#X>nOPUXaWTS0)w1{$W0AsyaM=U1pZqWO!Je$#Q~6M&B#SFlc}aY zGfTx#^is45a@vU$d3jVxo2TZPqZ8ab>)=SdH|pq&&?S^&V$>_PD&@cPOT%HuYQ6~k z`N7War!MT-v+Qg?jkfQ7c(336y0<-^nt*>dB){c6ZE)~J*d@%8k|qmnWmoZ|j97qT zbWNtxbalgF{=c1o@3hZL5_;JKRXiR?ws4;{UrVr?Hq*xk`J1CAxGsl6Iz_rC*c%3j(d zq11du202Vl23ih=T8f&k2Rwg=p#!{?hT^@XU{%9zHdTkFt~MJJ*BtaBJCP4epRGB} z*~x#dku6i1wHkIFGpc)1QZj7h(fQ`y3hi2izy=`eLoeiIF!YiC*HQN#}$ip>4Ownyl6@^k89s{Mwj}()A zYY1Q!cwVP1a5?r`%JwBzSzGo2?`SeG44uK^4P#o2uZmGh9gtDYNEbXAOu*e??Le`c$CxAdE#~^K`Vju(U^_1F)X4op2_XUNhsAl3 z`#-*Goc5Z9$hXaqOKlECbIB>tdc(Ir*02cQRJ)yTV!2bY;gIUHD6~WQ_*mF-kNq$p z;%Gc&d-s3n@d*j_qJe3sRrkgX21J;MiwkSA+aB3&(zd&{c+?3ZfB<*gDz19{Ps&}L zny1yEIV-g)t}Nn?8rod=o66Cue8(VWHV8MMg;73OY^ItREnsU>eIw=S%rkW(XSnNVlX23cIZR>zA@tp4wB-JHxpC zfq$(%#n}atYy^fIV1X%1PsC|B(f0fHbE}t-4gM%C@2Ve}Lp+6piGeP-M~?daaILvJ zX}JC&SZtlaX>1?3|7LGs{VR4o=K3mHvo=mx==zh+4sdD~oD^nfu5Y0rfQ1{}ufkoo z53PQK8x2^FpFcC^&(W)F!!A7u#NPE;wuO#dJrQ|k>2ncryn)_2AInDsuY8VM4g?_W z%OT05(YuT;@A54ku2PHnC`B@v7PMkVIF>o=80(EM$gRhTmh%2{;m<{8R1j$jb9J7N zzdnqugJvsk>L0OFLg|sr`9zk^pmtsvLHUq-A-}a!g_zep8oPDb+iKAunr6$g2|k+g z95gx@-M^NqV!-*hQG?b=MSOqQcqS?v9O%^HK$}PYj;7c&XQ@w8CpKRa6Kv%>p62U( zn%*oPzR6d$I@>RHg9~)66LH<7MUJ`~p_57gu0U(l$As`wJfhhNo7NJqJH?7J?Y_oM z2z%_&s_fSQqjj{0Em!o^n0Np|c37vB@RrwW{5vg2Gq6wc$^jHuH*N7vZ_bbG38%vF;8ajM!*PbV13CNXe$!$J~Uiy<4Q5VRbg$V7FHL%#e>p}EPq6#h_ zg8Y1QOH1Dt5?|!8$G1&EbpP1iR_mrFwEW%tpexsH z+i@Y242Ut5W&{nvv;`yjzuJfPOolf%k3nG?{;S%!&v^9dZuFlXUb=q8y=N2(|Hro? zbE(6HD}jdo#6ht)f2q9;bMhisR~YQrd>&{ z{lFI}Z^flC8iZe}WX!XJywpW@W?E@=M%2iLVO=K-K;@5dw8T2z<*LNjM-yA+I!1hrI6P|=gw@5cAV@b@}L z65w?vyv9iHW#IDu+cU1{lkX;9U6SOPYymTqOfXpBqRHd_Ccv0NFCpEUH}En;1Z?PP zY+1r?P{{A+B{4Ei$HgV3&Mzs?FRGqHuA)z_ zvGr5pOK8$#hH}a9uV&aMNyNG+qdk@gr{ws5cP8j92;^V`=g*wscu$D_JvV$*2jc~c zT@<>4O`RJdbD{LqrG59<{DqFFFQ^fL`kIN^U{A=P4Gc~a`8^QC#3HSPVFFP+5+D&U zhVF-+ABM%x*Zo1dFh@mn5i{41tzrNB4RH>3A(d}W&r`TJ``WlDupf5lJ7miQmV29` zwu1^cbxQl0nfwY-8H`#$tmlKqXg?z~qX+`bY(b61R^VV9zm7
  • usVSoVjI>$Q-YBnHldEW6 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 D;yMN8 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5028f28f..ffed3a25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708ff..1b6c7873 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 6d57edc7..ac1b06f9 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,89 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml index cfaadf17..9881599c 100644 --- a/grails-app/conf/application.yml +++ b/grails-app/conf/application.yml @@ -149,8 +149,13 @@ headerAndFooter: security: apikey: checkEnabled: true + enabled: true + auth: + serviceUrl: https://auth-test.ala.org.au/apikey/ws/check/ check: serviceUrl: https://auth-test.ala.org.au/apikey/ws/check?apikey= + userdetails: + serviceUrl: https://auth-test.ala.org.au/userdetails/userDetails/ cas: casServerName: https://auth-test.ala.org.au uriFilterPattern: diff --git a/grails-app/conf/logback.groovy b/grails-app/conf/logback.old.groovy similarity index 100% rename from grails-app/conf/logback.groovy rename to grails-app/conf/logback.old.groovy diff --git a/grails-app/conf/logback.xml b/grails-app/conf/logback.xml new file mode 100644 index 00000000..c64e87a0 --- /dev/null +++ b/grails-app/conf/logback.xml @@ -0,0 +1,17 @@ + + + + + + + + + UTF-8 + %clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex + + + + + + + \ No newline at end of file From 55d7cbc9a8abae03c084a475d9ce76f9b69a6112 Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:18:35 +1100 Subject: [PATCH 05/31] issue #181 updating to database-migration:4.2.0, adding io.reactivex.rxjava3 to build as io.reactivex.rxjava is no longer included with micronaut3, setting groovyVersion to 3.0.11, removing unnecessary qualifiers --- build.gradle | 3 ++- gradle.properties | 1 + .../ala/collectory/IdGeneratorService.groovy | 17 ++++++++++------- .../resources/gbif/GbifDataSourceAdapter.groovy | 8 ++------ 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index de23695a..88af6bcc 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { classpath "org.grails.plugins:hibernate5:${gormVersion}" classpath "gradle.plugin.com.github.erdi.webdriver-binaries:webdriver-binaries-gradle-plugin:$webdriverBinariesVersion" classpath "com.bertramlabs.plugins:asset-pipeline-gradle:3.4.6" - classpath 'org.grails.plugins:database-migration:4.0.0' + classpath 'org.grails.plugins:database-migration:4.2.0' } } @@ -102,6 +102,7 @@ dependencies { implementation "com.opencsv:opencsv:3.7" implementation "io.micronaut:micronaut-http-client" compileOnly "io.micronaut:micronaut-inject-groovy" + implementation "io.reactivex.rxjava3:rxjava:3.1.6" implementation "org.apache.httpcomponents:httpclient:4.5.13" implementation "commons-io:commons-io:2.11.0" diff --git a/gradle.properties b/gradle.properties index aed1a7e7..746ffad6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,7 @@ org.gradle.daemon=true org.gradle.parallel=true org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024M gebVersion=2.3 +groovyVersion=3.0.11 seleniumVersion=3.14.0 webdriverBinariesVersion=2.6 chromeDriverVersion=2.45.0 diff --git a/grails-app/services/au/org/ala/collectory/IdGeneratorService.groovy b/grails-app/services/au/org/ala/collectory/IdGeneratorService.groovy index 44b92bb2..ab6e1dfc 100644 --- a/grails-app/services/au/org/ala/collectory/IdGeneratorService.groovy +++ b/grails-app/services/au/org/ala/collectory/IdGeneratorService.groovy @@ -1,5 +1,7 @@ package au.org.ala.collectory import groovy.sql.Sql +import org.springframework.transaction.annotation.Propagation + import javax.sql.DataSource import groovy.sql.GroovyRowResult import org.springframework.transaction.annotation.Transactional @@ -7,9 +9,9 @@ import org.springframework.transaction.annotation.Transactional class IdGeneratorService implements Serializable { static transactional = true - javax.sql.DataSource dataSource + DataSource dataSource - public enum IdType { + enum IdType { collection('co'), institution('in'), dataProvider('dp'), @@ -19,20 +21,21 @@ class IdGeneratorService implements Serializable { tempDataResource('drt') public String prefix - public IdType(prefix) { + + IdType(prefix) { this.prefix = prefix } - public static IdType lookup(pf) { - return IdType.values().find {it.prefix == pf} as IdType + static IdType lookup(pf) { + return values().find {it.prefix == pf} as IdType } - public getIndex() { + def getIndex() { return ordinal() + 1 } }; - @Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRES_NEW) def getNextId(IdType type) { def sql = new Sql(dataSource) def id = type.getIndex() diff --git a/src/main/groovy/au/org/ala/collectory/resources/gbif/GbifDataSourceAdapter.groovy b/src/main/groovy/au/org/ala/collectory/resources/gbif/GbifDataSourceAdapter.groovy index 3e0c49cc..70600dee 100644 --- a/src/main/groovy/au/org/ala/collectory/resources/gbif/GbifDataSourceAdapter.groovy +++ b/src/main/groovy/au/org/ala/collectory/resources/gbif/GbifDataSourceAdapter.groovy @@ -8,18 +8,14 @@ import au.org.ala.collectory.exception.ExternalResourceException import au.org.ala.collectory.resources.DataSourceAdapter import au.org.ala.collectory.resources.TaskPhase import au.org.ala.collectory.GbifService -import grails.converters.JSON import groovy.json.JsonOutput import groovy.json.JsonSlurper -import io.micronaut.core.convert.ConversionService import io.micronaut.http.HttpRequest import io.micronaut.http.HttpResponse import io.micronaut.http.client.HttpClient -import io.reactivex.Flowable +import io.reactivex.rxjava3.core.Flowable import org.apache.commons.io.FileUtils -import org.apache.commons.io.IOUtils import org.grails.web.json.JSONObject -import org.reactivestreams.Publisher import org.slf4j.LoggerFactory import java.text.DateFormat @@ -250,7 +246,7 @@ class GbifDataSourceAdapter extends DataSourceAdapter { } HttpClient http = HttpClient.create(url) - Flowable> call = http.exchange(httpRequest, String.class) + Flowable> call = http.exchange(httpRequest, String.class) as Flowable> HttpResponse response = call.blockingFirst(); Optional message = response.getBody(String.class); From f59855ed961920b7a471d64cdf819f5ac5e6c384 Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Fri, 3 Mar 2023 15:56:56 +1100 Subject: [PATCH 06/31] issue #181 removing unused getPluginMessageKeys, updating from @GrailsCompileStatic to CompileDynamic to resolve compile errr as per https://github.com/grails/grails-core/issues/12440, removing unnecessary qualifiers --- .../au/org/ala/collectory/MessagesController.groovy | 4 +++- .../org/ala/collectory/MessagePropertiesTrait.groovy | 10 ++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/grails-app/controllers/au/org/ala/collectory/MessagesController.groovy b/grails-app/controllers/au/org/ala/collectory/MessagesController.groovy index 2af38192..526abc80 100644 --- a/grails-app/controllers/au/org/ala/collectory/MessagesController.groovy +++ b/grails-app/controllers/au/org/ala/collectory/MessagesController.groovy @@ -1,5 +1,7 @@ package au.org.ala.collectory +import org.springframework.web.servlet.support.RequestContextUtils + class MessagesController { def messageSource @@ -13,7 +15,7 @@ class MessagesController { * @return */ def i18n(String id) { - def locale = org.springframework.web.servlet.support.RequestContextUtils.getLocale(request) ?: request.locale + def locale = RequestContextUtils.getLocale(request) ?: request.locale def keys = messageSource.withTraits(MessagePropertiesTrait).getMessageKeys(locale) response.setHeader("Content-type", "text/plain; charset=UTF-8") def messages = keys.collect { "${it.key}=${it.value}" } diff --git a/src/main/groovy/au/org/ala/collectory/MessagePropertiesTrait.groovy b/src/main/groovy/au/org/ala/collectory/MessagePropertiesTrait.groovy index 762f894f..bb6835ff 100644 --- a/src/main/groovy/au/org/ala/collectory/MessagePropertiesTrait.groovy +++ b/src/main/groovy/au/org/ala/collectory/MessagePropertiesTrait.groovy @@ -1,16 +1,10 @@ package au.org.ala.collectory +import groovy.transform.CompileDynamic -import grails.compiler.GrailsCompileStatic -import groovy.transform.TypeCheckingMode - -@GrailsCompileStatic(TypeCheckingMode.SKIP) +@CompileDynamic trait MessagePropertiesTrait { Properties getMessageKeys(Locale locale) { this.getMergedProperties(locale).properties } - - Properties getPluginMessageKeys(Locale locale) { - this.getMergedPluginProperties(locale).properties - } } \ No newline at end of file From 49e5f4e21cdd9bc627c42d0aef8569a9d1908ad1 Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Mon, 6 Mar 2023 12:17:28 +1100 Subject: [PATCH 07/31] issue #181 updating to ala-ws-plugin:3.2.0-SNAPSHOT and upgrading app to 3.1.4-SNAPSHOT for cognito specific changes --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 88af6bcc..889ab35e 100644 --- a/build.gradle +++ b/build.gradle @@ -113,7 +113,7 @@ dependencies { implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.1.2" implementation "org.grails.plugins:ala-auth:5.2.0-SNAPSHOT" implementation "org.grails.plugins:ala-ws-security-plugin:4.4.0-SNAPSHOT" - implementation "org.grails.plugins:ala-ws-plugin:3.1.2" + implementation "org.grails.plugins:ala-ws-plugin:3.2.0-SNAPSHOT" implementation "org.grails.plugins:audit-logging:4.0.3" implementation 'dk.glasius:external-config:3.1.1' implementation "org.grails.plugins:ala-admin-plugin:2.3.0" From fa75e6fa2f7a21f0b723164b40f8d93ae6b5e870 Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Mon, 6 Mar 2023 12:17:37 +1100 Subject: [PATCH 08/31] issue #181 updating to ala-ws-plugin:3.2.0-SNAPSHOT and upgrading app to 3.1.4-SNAPSHOT for cognito specific changes --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 889ab35e..84010102 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ plugins { } -version "3.1.3-SNAPSHOT" +version "3.1.4-SNAPSHOT" group "au.org.ala" From d8eccdbad8bd8c5ac4643dfd18de914255eeb48a Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Wed, 8 Mar 2023 16:34:10 +1100 Subject: [PATCH 09/31] issue #181 updating to current implementation of jwt credentails validation methods from ala-ws-security plugin in collectory to maintain custom implementation for backwards compatiblity with custom API key usages --- grails-app/conf/application.yml | 2 +- .../CollectoryWebServicesInterceptor.groovy | 8 +- .../org/ala/collectory/DataController.groovy | 2 +- .../collectory/CollectoryAuthService.groovy | 144 +++++++++++++----- 4 files changed, 110 insertions(+), 46 deletions(-) diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml index 9881599c..3d927a11 100644 --- a/grails-app/conf/application.yml +++ b/grails-app/conf/application.yml @@ -151,7 +151,7 @@ security: checkEnabled: true enabled: true auth: - serviceUrl: https://auth-test.ala.org.au/apikey/ws/check/ + serviceUrl: https://auth-test.ala.org.au/apikey/ check: serviceUrl: https://auth-test.ala.org.au/apikey/ws/check?apikey= userdetails: diff --git a/grails-app/controllers/au/org/ala/collectory/CollectoryWebServicesInterceptor.groovy b/grails-app/controllers/au/org/ala/collectory/CollectoryWebServicesInterceptor.groovy index 9dd94bbd..736e31b4 100644 --- a/grails-app/controllers/au/org/ala/collectory/CollectoryWebServicesInterceptor.groovy +++ b/grails-app/controllers/au/org/ala/collectory/CollectoryWebServicesInterceptor.groovy @@ -34,19 +34,19 @@ class CollectoryWebServicesInterceptor { } boolean before() { // set default role requirement for protected ROLE_EDITOR as the same info is only available to ROLE_EDITOR via the UI. - String[] requiredRoles = [grailsApplication.config.ROLE_EDITOR] + String requiredRole = grailsApplication.config.ROLE_EDITOR // set gbifRegistrationRole role requirement for GBIF operations if(controllerName == 'gbif' || actionName == 'syncGBIF'){ - requiredRoles = [grailsApplication.config.gbifRegistrationRole] + requiredRole = grailsApplication.config.gbifRegistrationRole } // set ROLE_ADMIN role requirement for certain controllers and actions as per admin UI if( controllerName == 'ipt'){ - requiredRoles = [grailsApplication.config.ROLE_ADMIN] + requiredRole = grailsApplication.config.ROLE_ADMIN } - if (collectoryAuthService.isAuthorisedWsRequest(params, request, response, requiredRoles, null)) { + if (collectoryAuthService.isAuthorisedWsRequest(params, request, response, requiredRole, null)) { return true } log.warn("Denying access to $actionName from remote addr: ${request.remoteAddr}, remote host: ${request.remoteHost}") diff --git a/grails-app/controllers/au/org/ala/collectory/DataController.groovy b/grails-app/controllers/au/org/ala/collectory/DataController.groovy index bf8e3d6b..3755ab4e 100644 --- a/grails-app/controllers/au/org/ala/collectory/DataController.groovy +++ b/grails-app/controllers/au/org/ala/collectory/DataController.groovy @@ -449,7 +449,7 @@ class DataController { def entityInJson if (clazz == 'DataResource') { // this auth check (JWT or API key) is a special case handling to support backwards compatibility(which used to check for API key). - String [] requiredRoles = [grailsApplication.config.ROLE_ADMIN] + String requiredRoles = grailsApplication.config.ROLE_ADMIN def authCheck = collectoryAuthService.isAuthorisedWsRequest(getParams(), request, response, requiredRoles,null) entityInJson = crudService."read${clazz}"(params.pg, authCheck) } else { diff --git a/grails-app/services/au/org/ala/collectory/CollectoryAuthService.groovy b/grails-app/services/au/org/ala/collectory/CollectoryAuthService.groovy index 5b2faecf..2b983712 100644 --- a/grails-app/services/au/org/ala/collectory/CollectoryAuthService.groovy +++ b/grails-app/services/au/org/ala/collectory/CollectoryAuthService.groovy @@ -1,30 +1,42 @@ package au.org.ala.collectory +import au.org.ala.ws.security.ApiKeyClient +import au.org.ala.ws.security.CheckApiKeyResult +import au.org.ala.ws.security.JwtProperties +import au.org.ala.ws.security.client.AlaAuthClient import au.org.ala.ws.service.WebService import grails.web.servlet.mvc.GrailsParameterMap import org.pac4j.core.config.Config import org.pac4j.core.context.WebContext +import org.pac4j.core.context.session.SessionStore +import org.pac4j.core.context.session.SessionStoreFactory +import org.pac4j.core.credentials.Credentials import org.pac4j.core.profile.ProfileManager import org.pac4j.core.profile.UserProfile import org.pac4j.core.util.FindBest -import org.pac4j.http.client.direct.DirectBearerAuthClient import org.pac4j.jee.context.JEEContextFactory +import org.pac4j.oidc.credentials.OidcCredentials import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.context.request.RequestContextHolder +import retrofit2.Call +import retrofit2.Response import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse class CollectoryAuthService{ static transactional = false - def apiKeyService def grailsApplication def authService def providerGroupService + def apiKeyClient + + @Autowired + JwtProperties jwtProperties @Autowired(required = false) Config config @Autowired(required = false) - DirectBearerAuthClient directBearerAuthClient + AlaAuthClient alaAuthClient static final API_KEY_COOKIE = "ALA-API-Key" @@ -150,62 +162,114 @@ class CollectoryAuthService{ - def checkJWT(HttpServletRequest request, HttpServletResponse response, String[] requiredRoles, String[] requiredScopes) { + def checkJWT(HttpServletRequest request, HttpServletResponse response, String requiredRole, String requiredScope) { def result = false + + if (jwtProperties.enabled) { def context = context(request, response) - ProfileManager profileManager = new ProfileManager(context, config.sessionStore) + def sessionStore = sessionStore() + ProfileManager profileManager = new ProfileManager(context, sessionStore) profileManager.setConfig(config) - def credentials = directBearerAuthClient.getCredentials(context, config.sessionStore) - if (credentials.isPresent()) { - def profile = directBearerAuthClient.getUserProfile(credentials.get(), context, config.sessionStore) - if (profile.isPresent()) { - def userProfile = profile.get() + result = alaAuthClient.getCredentials(context, sessionStore) + .map { credentials -> checkCredentials(requiredScope, credentials, requiredRole, context, profileManager) } + } + return result + } + + /** + * Validate the given credentials against any required scope or role + * + * @param requiredScope The required scope for the access token, if any + * @param credentials The credentials, should be an OidcCredentials instance + * @param requiredRole The required role for the user, if any + * @param context The web context (request, response) + * @param profileManager The profile manager, the user profile if available, will be saved into this profile manager + * @return true if the credentials match both the requiredScope and requiredRole + */ + private boolean checkCredentials(String requiredScope, Credentials credentials, String requiredRole, WebContext context, ProfileManager profileManager) { + boolean matchesScope + if (requiredScope) { + + if (credentials instanceof OidcCredentials) { + + OidcCredentials oidcCredentials = credentials + + matchesScope = oidcCredentials.accessToken.scope.contains(requiredScope) + + if (!matchesScope) { + log.debug "access_token scopes '${oidcCredentials.accessToken.scope}' is missing required scopes ${requiredScope}" + } + } else { + matchesScope = false + log.debug("$credentials are not OidcCredentials, so can't get access_token") + } + } else { + matchesScope = true + } + + boolean matchesRole + Optional userProfile = alaAuthClient.getUserProfile(credentials, context, config.sessionStore) + .map { userProfile -> // save profile into profile manager to match pac4j filter profileManager.save( - directBearerAuthClient.getSaveProfileInSession(context, userProfile), + alaAuthClient.getSaveProfileInSession(context, userProfile), userProfile, - directBearerAuthClient.isMultiProfile(context, userProfile) + alaAuthClient.isMultiProfile(context, userProfile) ) - - result = true - if (result && requiredRoles) { - def roles = userProfile.roles - // check if the user profile has ROLE_ADMIN otherwise check their roles against required roles - def hasAdminRole = roles.contains(grailsApplication.config.ROLE_ADMIN) - result = hasAdminRole ?: requiredRoles.every() { - // set true if the user has the required role or the configured ROLE_ADMIN - roles.contains(it) - } + userProfile + } + if (requiredRole) { + matchesRole = userProfile + .map {profile -> checkProfileRole(profile, requiredRole) } + .orElseGet { + log.debug "rejecting request because role $requiredRole is required but no user profile is available" + false } + } else { + matchesRole = true + } - if (result && requiredScopes) { - def scope = userProfile.permissions //attributes['scope'] as List - result = requiredScopes.every { - scope.contains(it) - } - } + return matchesScope && matchesRole + } - } - } - return result + /** + * Checks that the given profile has the required role + * @param userProfile + * @param requiredRole + * @return true if the profile has the role, false otherwise + */ + boolean checkProfileRole(UserProfile userProfile, String requiredRole) { + def userProfileContainsRole = userProfile.roles.contains(requiredRole) + + if (!userProfileContainsRole) { + log.debug "user profile roles '${userProfile.roles}' is missing required role ${requiredRole}" + } + return userProfileContainsRole } - private WebContext context(HttpServletRequest request, HttpServletResponse response) { + private WebContext context(request, response) { final WebContext context = FindBest.webContextFactory(null, config, JEEContextFactory.INSTANCE).newContext(request, response) return context } - def isAuthorisedWsRequest(GrailsParameterMap params, HttpServletRequest request, HttpServletResponse response, String[] requiredRoles, String[] requiredScopes){ - Boolean authorised - // check for JWT first - authorised = checkJWT(request, response, requiredRoles, requiredScopes) - // if still unauthorised, check for and attempt to validate API key - if(!authorised && grailsApplication.config.security.apikey.checkEnabled.toBoolean()){ + private SessionStore sessionStore() { + final SessionStore sessionStore = FindBest.sessionStoreFactory(null, config, JEEContextFactory.INSTANCE as SessionStoreFactory).newSessionStore() + return sessionStore + } + + def isAuthorisedWsRequest(GrailsParameterMap params, HttpServletRequest request, HttpServletResponse response, String requiredRole, String requiredScope){ + Boolean authorised = false + if(grailsApplication.config.security.apikey.checkEnabled.toBoolean() || grailsApplication.config.security.apikey.enabled.toBoolean()){ def apiKey = getApiKey(params, request) - def apiKeyResponse = apiKeyService.checkApiKey(apiKey) - authorised = apiKeyResponse.valid + Call checkApiKeyCall = apiKeyClient.checkApiKey(apiKey) + final Response checkApiKeyResponse = checkApiKeyCall.execute() + CheckApiKeyResult apiKeyCheck = checkApiKeyResponse.body(); + authorised = apiKeyCheck.isValid() } + if(!authorised){ + authorised = checkJWT(request, response, requiredRole, requiredScope) + } return authorised } From 5224391a49b8370fbccb1715bc969f2b1b1c8517 Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Wed, 15 Mar 2023 16:31:16 +1100 Subject: [PATCH 10/31] issue #181 updating security project plugins to 6.0.0-SNAPSHOT --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 84010102..3dc31a9b 100644 --- a/build.gradle +++ b/build.gradle @@ -111,9 +111,9 @@ dependencies { implementation "mysql:mysql-connector-java:8.0.22" implementation "org.grails.plugins:ala-bootstrap3:4.1.0" implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.1.2" - implementation "org.grails.plugins:ala-auth:5.2.0-SNAPSHOT" - implementation "org.grails.plugins:ala-ws-security-plugin:4.4.0-SNAPSHOT" - implementation "org.grails.plugins:ala-ws-plugin:3.2.0-SNAPSHOT" + implementation "org.grails.plugins:ala-auth:6.0.0-SNAPSHOT" + implementation "org.grails.plugins:ala-ws-security-plugin:6.0.0-SNAPSHOT" + implementation "org.grails.plugins:ala-ws-plugin:6.0.0-SNAPSHOT" implementation "org.grails.plugins:audit-logging:4.0.3" implementation 'dk.glasius:external-config:3.1.1' implementation "org.grails.plugins:ala-admin-plugin:2.3.0" From dd4e99535a9951119c13e2e5d86205f0e5eb369c Mon Sep 17 00:00:00 2001 From: vjrj Date: Wed, 22 Mar 2023 19:00:22 +0100 Subject: [PATCH 11/31] Fix for 182 --- grails-app/views/dataHub/members.gsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grails-app/views/dataHub/members.gsp b/grails-app/views/dataHub/members.gsp index 161a9e46..98c7e8a9 100644 --- a/grails-app/views/dataHub/members.gsp +++ b/grails-app/views/dataHub/members.gsp @@ -19,7 +19,7 @@ - +
    From f4f821dfb144c99f10107f39aee5be42051a5530 Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Thu, 6 Apr 2023 14:00:06 +1000 Subject: [PATCH 12/31] issue #181 updating to usage of grails5 verison of ala-charts-plugin i.e. 2.3.0-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3dc31a9b..bdae6b5c 100644 --- a/build.gradle +++ b/build.gradle @@ -110,7 +110,7 @@ dependencies { // before updating mysql-connector-java implementation "mysql:mysql-connector-java:8.0.22" implementation "org.grails.plugins:ala-bootstrap3:4.1.0" - implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.1.2" + implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.3.0-SNAPSHOT" implementation "org.grails.plugins:ala-auth:6.0.0-SNAPSHOT" implementation "org.grails.plugins:ala-ws-security-plugin:6.0.0-SNAPSHOT" implementation "org.grails.plugins:ala-ws-plugin:6.0.0-SNAPSHOT" From 939cfedfc8f83f92a32f7884353dffc00b8370ec Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Mon, 1 May 2023 17:30:00 +1000 Subject: [PATCH 13/31] issue #181 updating security project plugins to 6.0.0 --- build.gradle | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index bdae6b5c..7bd6290f 100644 --- a/build.gradle +++ b/build.gradle @@ -76,6 +76,8 @@ sourceSets { } } +def alaSecurityLibsVersion='6.0.0' + dependencies { developmentOnly("org.springframework.boot:spring-boot-devtools") implementation "org.springframework.boot:spring-boot-starter-logging" @@ -111,9 +113,9 @@ dependencies { implementation "mysql:mysql-connector-java:8.0.22" implementation "org.grails.plugins:ala-bootstrap3:4.1.0" implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.3.0-SNAPSHOT" - implementation "org.grails.plugins:ala-auth:6.0.0-SNAPSHOT" - implementation "org.grails.plugins:ala-ws-security-plugin:6.0.0-SNAPSHOT" - implementation "org.grails.plugins:ala-ws-plugin:6.0.0-SNAPSHOT" + implementation "org.grails.plugins:ala-auth:${alaSecurityLibsVersion}" + implementation "org.grails.plugins:ala-ws-security-plugin:${alaSecurityLibsVersion}" + implementation "org.grails.plugins:ala-ws-plugin:${alaSecurityLibsVersion}" implementation "org.grails.plugins:audit-logging:4.0.3" implementation 'dk.glasius:external-config:3.1.1' implementation "org.grails.plugins:ala-admin-plugin:2.3.0" From 5565b6f9261ced2d84eeb4902387d876cd3cbb1f Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Tue, 2 May 2023 13:16:07 +1000 Subject: [PATCH 14/31] issue #181 moving alaSecurityLibsVersion to gradle.properties, bumping minor version for Grails5 and Gradle7 upgrade --- build.gradle | 10 ++++------ gradle.properties | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 7bd6290f..aa199ff0 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,7 @@ plugins { } -version "3.1.4-SNAPSHOT" +version "3.2.0-SNAPSHOT" group "au.org.ala" @@ -76,8 +76,6 @@ sourceSets { } } -def alaSecurityLibsVersion='6.0.0' - dependencies { developmentOnly("org.springframework.boot:spring-boot-devtools") implementation "org.springframework.boot:spring-boot-starter-logging" @@ -113,9 +111,9 @@ dependencies { implementation "mysql:mysql-connector-java:8.0.22" implementation "org.grails.plugins:ala-bootstrap3:4.1.0" implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.3.0-SNAPSHOT" - implementation "org.grails.plugins:ala-auth:${alaSecurityLibsVersion}" - implementation "org.grails.plugins:ala-ws-security-plugin:${alaSecurityLibsVersion}" - implementation "org.grails.plugins:ala-ws-plugin:${alaSecurityLibsVersion}" + implementation "org.grails.plugins:ala-auth:$alaSecurityLibsVersion" + implementation "org.grails.plugins:ala-ws-security-plugin:$alaSecurityLibsVersion" + implementation "org.grails.plugins:ala-ws-plugin:$alaSecurityLibsVersion" implementation "org.grails.plugins:audit-logging:4.0.3" implementation 'dk.glasius:external-config:3.1.1' implementation "org.grails.plugins:ala-admin-plugin:2.3.0" diff --git a/gradle.properties b/gradle.properties index 746ffad6..4ed655d6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,4 +9,5 @@ seleniumVersion=3.14.0 webdriverBinariesVersion=2.6 chromeDriverVersion=2.45.0 geckodriverVersion=0.24.0 -seleniumSafariDriverVersion=3.14.0 \ No newline at end of file +seleniumSafariDriverVersion=3.14.0 +alaSecurityLibsVersion=6.0.0 \ No newline at end of file From c55456b7531dbbf7647c975c06910c27e189cccb Mon Sep 17 00:00:00 2001 From: Sushant <105408469+sughics@users.noreply.github.com> Date: Wed, 3 May 2023 10:47:41 +1000 Subject: [PATCH 15/31] issue #181 updating to ala-charts-plugin:2.3.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index aa199ff0..f334b864 100644 --- a/build.gradle +++ b/build.gradle @@ -110,7 +110,7 @@ dependencies { // before updating mysql-connector-java implementation "mysql:mysql-connector-java:8.0.22" implementation "org.grails.plugins:ala-bootstrap3:4.1.0" - implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.3.0-SNAPSHOT" + implementation "au.org.ala.plugins.grails:ala-charts-plugin:2.3.0" implementation "org.grails.plugins:ala-auth:$alaSecurityLibsVersion" implementation "org.grails.plugins:ala-ws-security-plugin:$alaSecurityLibsVersion" implementation "org.grails.plugins:ala-ws-plugin:$alaSecurityLibsVersion" From e57e317e8b3840140a4f0bedc3b09f604f2aa5f2 Mon Sep 17 00:00:00 2001 From: vjrj Date: Tue, 6 Jun 2023 15:58:43 +0200 Subject: [PATCH 16/31] Fix for #188 (#189) --- .../collectory/ProviderGroupController.groovy | 22 +++++++++++++++---- grails-app/views/shared/location.gsp | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/grails-app/controllers/au/org/ala/collectory/ProviderGroupController.groovy b/grails-app/controllers/au/org/ala/collectory/ProviderGroupController.groovy index 2226b2dd..510ad193 100644 --- a/grails-app/controllers/au/org/ala/collectory/ProviderGroupController.groovy +++ b/grails-app/controllers/au/org/ala/collectory/ProviderGroupController.groovy @@ -5,7 +5,9 @@ import grails.converters.JSON import org.springframework.dao.DataIntegrityViolationException import org.springframework.web.context.request.RequestContextHolder import org.springframework.web.multipart.MultipartFile - +import java.text.NumberFormat +import java.text.ParseException +import org.springframework.web.context.request.ServletRequestAttributes /** * This is a base class for all provider group entities types. * @@ -290,9 +292,19 @@ abstract class ProviderGroupController { if (pg) { if (checkLocking(pg,'/shared/location')) { return } - // special handling for lat & long - if (!params.latitude) { params.latitude = -1 } - if (!params.longitude) { params.longitude = -1 } + Locale userLocale = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request.locale + NumberFormat numberFormat = NumberFormat.getNumberInstance(userLocale) + + double latitude + double longitude + + try { + latitude = params.latitude ? numberFormat.parse(params.latitude).doubleValue() : -1 + longitude = params.longitude ? numberFormat.parse(params.longitude).doubleValue() : -1 + } catch (ParseException e) { + latitude = -1 + longitude = -1 + } // special handling for embedded address - need to create address obj if none exists and we have data if (!pg.address && [params.address?.street, params.address?.postBox, params.address?.city, @@ -301,6 +313,8 @@ abstract class ProviderGroupController { } pg.properties = params + pg.latitude = latitude + pg.longitude = longitude pg.userLastModified = collectoryAuthService?.username() if (!pg.hasErrors() ) { diff --git a/grails-app/views/shared/location.gsp b/grails-app/views/shared/location.gsp index f403cc08..6f3f7e11 100644 --- a/grails-app/views/shared/location.gsp +++ b/grails-app/views/shared/location.gsp @@ -84,7 +84,7 @@ - +
    From 6524784fce2528c160e4c2733a7ce14620dd52f7 Mon Sep 17 00:00:00 2001 From: vjrj Date: Tue, 6 Jun 2023 15:59:30 +0200 Subject: [PATCH 17/31] Add lastUpdate and num of records to datasets page (#183) * Add lastUpdate and num of records to datasets page * Better moment.js date format --- grails-app/assets/javascripts/application.js | 1 + grails-app/assets/javascripts/datasets.js | 83 ++++++++++++++----- .../javascripts/moment-with-locales.min.js | 2 + grails-app/assets/stylesheets/main.css | 2 +- grails-app/conf/application.yml | 2 + .../ala/collectory/PublicController.groovy | 2 +- grails-app/i18n/messages.properties | 3 + grails-app/views/public/dataSets.gsp | 6 +- 8 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 grails-app/assets/javascripts/moment-with-locales.min.js diff --git a/grails-app/assets/javascripts/application.js b/grails-app/assets/javascripts/application.js index 098f6cd1..8084b6f9 100644 --- a/grails-app/assets/javascripts/application.js +++ b/grails-app/assets/javascripts/application.js @@ -8,5 +8,6 @@ //= require jquery-3.3.1.min //= require jquery.i18n.properties-1.0.9.min //= require jquery.ba-bbq.min +//= require moment-with-locales.min.js //= require datasets.js //= require_self diff --git a/grails-app/assets/javascripts/datasets.js b/grails-app/assets/javascripts/datasets.js index 89e44750..cb79ebad 100644 --- a/grails-app/assets/javascripts/datasets.js +++ b/grails-app/assets/javascripts/datasets.js @@ -32,6 +32,9 @@ var allResources; /* holds current filtered list */ var resources; +/* holds resources facet for stats */ +var rersourcesStats; + /* list of filters currently in effect - items are {name, value} */ var currentFilters = []; @@ -50,29 +53,50 @@ var biocacheUrl; /* options for all tooltips */ var tooltipOptions = {position:{my: 'left bottom', at: 'center top-10'}, show: {duration: 130, effect:'fade'}, hide: {duration: 200, effect:'fade'}, collision: "fit"}; +function drCount(dr) { + try { + return resourcesStats[0].fieldResult.find((result) => result.fq.includes(dr)).count; + } + catch (e) { + return -1; + } +} + /** load resources and show first page **/ -function loadResources(serverUrl, biocacheRecordsUrl) { +function loadResources(serverUrl, biocacheRecordsUrl, biocacheServicesUrl) { baseUrl = serverUrl; biocacheUrl = biocacheRecordsUrl; - $.getJSON(baseUrl + "/public/condensed.json", function(data) { - allResources = data; - // no filtering at this stage - resources = allResources; - - setStateFromHash(); - updateTotal(); - calculateFacets(); - showFilters(); - resources.sort(comparator); - displayPage(); - wireDownloadLink(); - wireSearchLink(); - - // set up tooltips - // - don't do download link because the title changes and the tooltip app does not update - // - also limit to content to exclude links in header - $('div.collectory-content [title][id!="downloadLink"]'); - }); + + function loadResourcesCondensed() { + $.getJSON(baseUrl + "/public/condensed.json", function (data) { + allResources = data; + // no filtering at this stage + resources = allResources; + + setStateFromHash(); + updateTotal(); + calculateFacets(); + showFilters(); + resources.sort(comparator); + displayPage(); + wireDownloadLink(); + wireSearchLink(); + + // set up tooltips + // - don't do download link because the title changes and the tooltip app does not update + // - also limit to content to exclude links in header + $('div.collectory-content [title][id!="downloadLink"]'); + }); + } + + if (COLLECTORY_CONF.showExtraInfoInDataSetsView) { + $.getJSON(biocacheServicesUrl + "/occurrence/facets?facets=data_resource_uid&pageSize=0&flimit=-1", function(dataStats) { + resourcesStats = dataStats; + loadResourcesCondensed(); + }); + } else { + loadResourcesCondensed(); + } } /*************************************************\ * List display @@ -107,6 +131,14 @@ function appendResource(value) { var $rowB = $('

    ').appendTo($div); var $rowC = $('