diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 000000000..c971dc3b9 --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,17 @@ +github: + environments: + source: + required_reviewers: + - id: grails-committers + type: Team + wait_timer: 0 + release: + required_reviewers: + - id: grails-committers + type: Team + wait_timer: 0 + docs: + required_reviewers: + - id: grails-committers + type: Team + wait_timer: 0 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 07d88f0d6..43a1e53c7 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -29,7 +29,7 @@ concurrency: cancel-in-progress: false jobs: coreTests: - if: ${{ contains(github.event.head_commit.message, '[skip tests]') }} + if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }} runs-on: ubuntu-24.04 steps: - name: "📥 Checkout repository" @@ -46,7 +46,7 @@ jobs: - name: "🏃‍♂️ Run Tests" run: ./gradlew check --max-workers=2 --refresh-dependencies --continue functionalTests: - if: ${{ contains(github.event.head_commit.message, '[skip tests]') }} + if: ${{ !contains(github.event.head_commit.message, '[skip tests]') }} runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -68,7 +68,7 @@ jobs: run: ./gradlew core-examples-functional-test-app:check -DTESTCONFIG=${{ matrix.test-config }} publish: needs: [ coreTests, functionalTests ] - if: ${{ always() && github.repository_owner == 'apache' && github.event_name == 'push' && (needs.coreTests.result == 'success' || needs.coreTests.result == 'skipped') && (needs.functionalTests.result == 'success' || needs.functionalTests.result == 'skipped') }} + if: ${{ always() && github.repository_owner == 'apache' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (needs.coreTests.result == 'success' || needs.coreTests.result == 'skipped') && (needs.functionalTests.result == 'success' || needs.functionalTests.result == 'skipped') }} runs-on: ubuntu-24.04 permissions: contents: write diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf5a83365..2b5b528ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,9 +31,14 @@ jobs: runs-on: ubuntu-24.04 outputs: release_version: ${{ steps.release_version.outputs.value }} + extract_repository_name: ${{ steps.extract_repository_name.outputs.repository_name }} steps: - name: "Output Agent IP" # in the event RAO blocks this agent, this can be used to debug it run: curl -s https://api.ipify.org + - name: "Extract repository name" + id: extract_repository_name + run: | + echo "repository_name=${GITHUB_REPOSITORY##*/}" >> $GITHUB_OUTPUT - name: "📥 Checkout repository" uses: actions/checkout@v4 - name: 'Ensure Common Build Date' # to ensure a reproducible build @@ -51,7 +56,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: liberica - java-version: '17.0.14' # this must be a specific version for reproducible builds + java-version: '17.0.15' # this must be a specific version for reproducible builds - name: "🐘 Setup Gradle" uses: gradle/actions/setup-gradle@v4 with: @@ -70,12 +75,26 @@ jobs: NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_STAGE_DEPLOYER_PW }} NEXUS_PUBLISH_URL: 'https://repository.apache.org/service/local/' NEXUS_PUBLISH_STAGING_PROFILE_ID: ${{ secrets.STAGING_PROFILE_ID }} - NEXUS_PUBLISH_DESCRIPTION: 'grails-spring-security:${{ steps.release_version.outputs.value }}' + NEXUS_PUBLISH_DESCRIPTION: '${{ steps.extract_repository_name.outputs.repository_name }}:${{ steps.release_version.outputs.value }}' SIGNING_KEY: ${{ secrets.GPG_KEY_ID }} run: > - ./gradlew --refresh-dependencies + ./gradlew publishToSonatype closeSonatypeStagingRepository + aggregateChecksums + aggregatePublishedArtifacts + - name: "Upload checksums" + uses: softprops/action-gh-release@v2 + with: + files: build/CHECKSUMS.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Upload published artifacts" + uses: softprops/action-gh-release@v2 + with: + files: build/PUBLISHED_ARTIFACTS.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: "Generate Build Date file" run: echo "$SOURCE_DATE_EPOCH" >> build/BUILD_DATE.txt - name: "Upload Build Date file" @@ -84,6 +103,133 @@ jobs: files: build/BUILD_DATE.txt env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + source: + # to ensure we never publish any build artifacts, run the source distribution as a separate build workflow + environment: source + name: "Source Distribution" + needs: publish + permissions: + contents: write + runs-on: ubuntu-latest + outputs: + extract_repository_name: ${{ steps.extract_repository_name.outputs.repository_name }} + steps: + - name: "Extract repository name" + id: extract_repository_name + run: | + echo "repository_name=${GITHUB_REPOSITORY##*/}" >> $GITHUB_OUTPUT + - name: "📥 Checkout repository" + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + ref: ${{ github.ref_name }} + path: project + - name: "🗑️ Remove unnecessary files" + run: | + rm -f project/gradle/wrapper/gradle-wrapper.jar + rm -f project/gradle/wrapper/gradle-wrapper.properties + rm -f project/gradlew + rm -f project/.asf.yaml + - name: "Download CHECKSUMS.txt and rename to CHECKSUMS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd project + release_url=$(gh release view ${{ github.ref_name }} --json assets --repo ${{ github.repository }} --jq '.assets[] | select(.name == "CHECKSUMS.txt") | .url') + curl -L -H "Authorization: token $GH_TOKEN" -o CHECKSUMS "$release_url" + - name: "Download PUBLISHED_ARTIFACTS.txt and rename to PUBLISHED_ARTIFACTS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd project + release_url=$(gh release view ${{ github.ref_name }} --json assets --repo ${{ github.repository }} --jq '.assets[] | select(.name == "PUBLISHED_ARTIFACTS.txt") | .url') + curl -L -H "Authorization: token $GH_TOKEN" -o PUBLISHED_ARTIFACTS "$release_url" + - name: "Download BUILD_DATE.txt and rename to BUILD_DATE" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cd project + release_url=$(gh release view ${{ github.ref_name }} --json assets --repo ${{ github.repository }} --jq '.assets[] | select(.name == "BUILD_DATE.txt") | .url') + curl -L -H "Authorization: token $GH_TOKEN" -o BUILD_DATE "$release_url" + - name: "Ensure source files use common date" + run: | + SOURCE_DATE_EPOCH=$(cat project/BUILD_DATE) + find . -depth \( -type f -o -type d \) -exec touch -d "@${SOURCE_DATE_EPOCH}" {} + + - name: "📦 Create source distribution ZIP" + run: | + version="${{ github.ref_name }}" + version="${version#v}" # Strip 'v' prefix + zip -r "apache-${{ steps.extract_repository_name.outputs.repository_name }}-${version}-incubating-src.zip" project -x 'project/.git/*' -x 'project/.github/*' + - name: '🔐 Set up GPG' + run: | + echo "${{ secrets.GRAILS_GPG_KEY }}" | gpg --batch --import + gpg --list-keys + env: + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + - name: "🔏 Sign source distribution ZIP" + env: + GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} + run: | + version="${{ github.ref_name }}" + version="${version#v}" # Strip 'v' prefix + gpg --default-key "${GPG_KEY_ID}" --batch --yes --pinentry-mode loopback --armor --detach-sign apache-${{ steps.extract_repository_name.outputs.repository_name }}-${version}-incubating-src.zip + - name: "📦 Create source distribution checksum" + run: | + version="${{ github.ref_name }}" + version="${version#v}" # Strip 'v' prefix + sha512sum apache-${{ steps.extract_repository_name.outputs.repository_name }}-${version}-incubating-src.zip > "apache-${{ steps.extract_repository_name.outputs.repository_name }}-${version}-incubating-src.zip.sha512" + - name: "🚀 Upload ZIP and Signature to GitHub Release" + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.ref_name }} + files: | + apache-${{ steps.extract_repository_name.outputs.repository_name }}-*-incubating-src.zip + apache-${{ steps.extract_repository_name.outputs.repository_name }}-*-incubating-src.zip.sha512 + apache-${{ steps.extract_repository_name.outputs.repository_name }}-*-incubating-src.zip.asc + - name: "Remove CHECKSUMS.txt asset from release" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + cd project + gh release --repo ${{ github.repository }} delete-asset ${{ github.ref_name }} CHECKSUMS.txt --yes + - name: "Remove BUILD_DATE.txt asset from release" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + cd project + gh release --repo ${{ github.repository }} delete-asset ${{ github.ref_name }} BUILD_DATE.txt --yes + - name: "Remove PUBLISHED_ARTIFACTS.txt asset from release" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + cd project + gh release --repo ${{ github.repository }} delete-asset ${{ github.ref_name }} PUBLISHED_ARTIFACTS.txt --yes + release: + environment: release + needs: [publish, source] + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + steps: + - name: "📥 Checkout repository" + uses: actions/checkout@v4 + with: + ref: v${{ needs.publish.outputs.release_version }} + - name: "☕️ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: liberica + java-version: '17.0.15' # this must be a specific version for reproducible builds + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + with: + develocity-access-key: ${{ secrets.GRAILS_DEVELOCITY_ACCESS_KEY }} + - name: "⚙️ Run post-release" + uses: apache/grails-github-actions/post-release@asf docs: environment: docs name: "Publish Documentation" @@ -97,12 +243,10 @@ jobs: uses: actions/checkout@v4 with: ref: v${{ needs.publish.outputs.release_version }} - - name: 'Ensure Common Build Date' # to ensure a reproducible build - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" - name: "☕️ Setup JDK" uses: actions/setup-java@v4 with: - java-version: 17 + java-version: '17.0.15' # this must be a specific version for reproducible builds distribution: liberica - name: "🐘 Setup Gradle" uses: gradle/actions/setup-gradle@v4 @@ -118,29 +262,4 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GRADLE_PUBLISH_RELEASE: 'true' SOURCE_FOLDER: build/docs - VERSION: ${{ steps.release_version.outputs.value }} - release: - environment: release - needs: publish - runs-on: ubuntu-latest - permissions: - contents: write - issues: write - steps: - - name: "📥 Checkout repository" - uses: actions/checkout@v4 - with: - ref: v${{ needs.publish.outputs.release_version }} - - name: 'Ensure Common Build Date' # to ensure a reproducible build - run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" - - name: "☕️ Setup JDK" - uses: actions/setup-java@v4 - with: - distribution: liberica - java-version: 17 - - name: "🐘 Setup Gradle" - uses: gradle/actions/setup-gradle@v4 - with: - develocity-access-key: ${{ secrets.GRAILS_DEVELOCITY_ACCESS_KEY }} - - name: "⚙️ Run post-release" - uses: apache/grails-github-actions/post-release@asf \ No newline at end of file + VERSION: ${{ needs.publish.outputs.release_version }} \ No newline at end of file diff --git a/.sdkmanrc b/.sdkmanrc index 99738804c..6e0cde3a1 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -1 +1,2 @@ java=17.0.15-librca +gradle=8.14 diff --git a/README.md b/README.md index 4a0d7c4e4..fd8afe0e0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,27 @@ limitations under the License. Grails Spring Security ====================== -See [documentation](https://apache.github.io/grails-spring-security/latest) for further information. +See [documentation](https://apache.github.io/grails-spring-security/latest) for detailed information. + +### Building + +To build this project from source, first bootstrap gradle: + + cd gradle-bootstrap + gradle + cd - + +After bootstrap the project, you can build it with the command: + + ./gradlew build + +To run the build only, and skip the tests, run: + + ./gradlew build -PskipTests + +Then publish the jar files to mavenLocal for usage: + + ./gradlew publishToMavenLocal ### Branch structure @@ -44,19 +64,3 @@ spring: - org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration - org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration ``` - -### Building - -To build this project from source, first bootstrap gradle: - - cd gradle-bootstrap - gradle - cd - - -After bootstrap the project, you can build it with the command: - - ./gradlew build - -To run the build only, and skip the tests, run: - - ./gradlew build -PskipTests \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3b9dedc42..e8bd1d0b7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,3 @@ -import java.time.Instant -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter - /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -20,6 +16,9 @@ import java.time.format.DateTimeFormatter * specific language governing permissions and limitations * under the License. */ +import java.time.Instant +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter ext { isReproducibleBuild = System.getenv("SOURCE_DATE_EPOCH") != null @@ -45,6 +44,15 @@ allprojects { } } } + + configurations.configureEach { + resolutionStrategy.eachDependency { DependencyResolveDetails details -> + if (details.requested.group == 'org.seleniumhq.selenium') { + details.useVersion('4.25.0') + details.because('Temporary workaround because of https://issues.chromium.org/issues/42323769') + } + } + } } subprojects { diff --git a/gradle.properties b/gradle.properties index 72bae2787..9990dacd7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,16 +17,17 @@ # under the License. # projectVersion=7.0.0-SNAPSHOT -grailsVersion=7.0.0-SNAPSHOT +grailsVersion=7.0.0-M4 javaVersion=17 +unboundidLdapSdk=7.0.2 apacheDsVersion=1.5.4 asciidoctorGradlePluginVersion=4.0.4 casClientCoreVersion=4.0.4 commonsLangVersion=2.6 dumbsterVersion=1.6 gradleCryptoChecksumVersion=1.4.0 -grailsRedisVersion=5.0.0-M3 +grailsRedisVersion=5.0.0-SNAPSHOT mailVersion=5.0.0-SNAPSHOT micronautVersion=4.5.3 pac4jVersion=6.0.6 diff --git a/gradle/publish-root-config.gradle b/gradle/publish-root-config.gradle index 89525e464..d2fdcf65f 100644 --- a/gradle/publish-root-config.gradle +++ b/gradle/publish-root-config.gradle @@ -53,7 +53,7 @@ tasks.register("aggregateChecksums").configure { group = "publishing" description = "Aggregates all SHA-256 checksums from subprojects into a single file." - def outputFileProvider = rootProject.layout.buildDirectory.file("grails-spring-security-checksums.txt") + def outputFileProvider = rootProject.layout.buildDirectory.file("CHECKSUMS.txt") outputs.file(outputFileProvider) dependsOn(subprojects.findResults {it.tasks.names.contains('publishedChecksums') ? "${it.path}:publishedChecksums" : null }) @@ -89,7 +89,7 @@ aggregatePublishedArtifacts.configure { group = "publishing" description = "Aggregates all published artifacts from subprojects into a single file." - def outputFileProvider = rootProject.layout.buildDirectory.file("grails-spring-security-artifacts.txt") + def outputFileProvider = rootProject.layout.buildDirectory.file("PUBLISHED_ARTIFACTS.txt") outputs.file(outputFileProvider) outputs.upToDateWhen { false } // not worth caching diff --git a/plugin-acl/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy b/plugin-acl/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy index 1120f3004..4b1ec44b7 100644 --- a/plugin-acl/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy +++ b/plugin-acl/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy @@ -25,7 +25,7 @@ class LoginPage extends Page { static url = 'login/auth' - static at = { title == 'Login' } + static at = { waitFor { title == 'Login' } } static content = { loginForm { $('form') } diff --git a/plugin-acl/examples/functional-test-app/src/integration-test/groovy/test/AdminFunctionalSpec.groovy b/plugin-acl/examples/functional-test-app/src/integration-test/groovy/test/AdminFunctionalSpec.groovy index 7838da1de..44da219b3 100644 --- a/plugin-acl/examples/functional-test-app/src/integration-test/groovy/test/AdminFunctionalSpec.groovy +++ b/plugin-acl/examples/functional-test-app/src/integration-test/groovy/test/AdminFunctionalSpec.groovy @@ -69,6 +69,7 @@ class AdminFunctionalSpec extends AbstractSecuritySpec { void 'view all'() { when: go "report/show?number=$i" + waitFor { title == 'Show Report' } then: assertContentContains "report$i" diff --git a/plugin-core/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy b/plugin-core/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy index 4503c1ea5..dc5524518 100644 --- a/plugin-core/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy +++ b/plugin-core/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy @@ -25,7 +25,7 @@ class LoginPage extends Page { static url = 'login/auth' - static at = { title == 'Login' } + static at = { waitFor { title == 'Login' } } static content = { loginForm { $('form') } diff --git a/plugin-core/examples/misc-functional-test-app/grails-spring-security-group/src/integration-test/groovy/demo/LoginPage.groovy b/plugin-core/examples/misc-functional-test-app/grails-spring-security-group/src/integration-test/groovy/demo/LoginPage.groovy index f98134033..64c54945a 100644 --- a/plugin-core/examples/misc-functional-test-app/grails-spring-security-group/src/integration-test/groovy/demo/LoginPage.groovy +++ b/plugin-core/examples/misc-functional-test-app/grails-spring-security-group/src/integration-test/groovy/demo/LoginPage.groovy @@ -24,9 +24,7 @@ import geb.Page class LoginPage extends Page { static url = "login/auth" - static at = { - title == "Login" - } + static at = { waitFor { title == 'Login' } } static content = { loginButton { $("#submit", 0) } diff --git a/plugin-core/examples/misc-functional-test-app/grails-spring-security-hierarchical-roles/src/integration-test/groovy/demo/LoginPage.groovy b/plugin-core/examples/misc-functional-test-app/grails-spring-security-hierarchical-roles/src/integration-test/groovy/demo/LoginPage.groovy index 42b4b10c1..023e8cedc 100644 --- a/plugin-core/examples/misc-functional-test-app/grails-spring-security-hierarchical-roles/src/integration-test/groovy/demo/LoginPage.groovy +++ b/plugin-core/examples/misc-functional-test-app/grails-spring-security-hierarchical-roles/src/integration-test/groovy/demo/LoginPage.groovy @@ -24,9 +24,7 @@ import geb.Page class LoginPage extends Page { static url = "login/auth" - static at = { - title == "Login" - } + static at = { waitFor { title == 'Login' } } static content = { loginButton { $("#submit", 0) } diff --git a/plugin-ldap/examples/custom-user-details-context-mapper/build.gradle b/plugin-ldap/examples/custom-user-details-context-mapper/build.gradle index fcfea0c51..f77f33faa 100644 --- a/plugin-ldap/examples/custom-user-details-context-mapper/build.gradle +++ b/plugin-ldap/examples/custom-user-details-context-mapper/build.gradle @@ -37,6 +37,9 @@ dependencies { implementation 'org.webjars:bootstrap:4.1.3' implementation 'org.webjars:jquery:3.3.1' + // to not depend on an external ldap server + implementation "com.unboundid:unboundid-ldapsdk:$unboundidLdapSdk" + runtimeOnly 'com.bertramlabs.plugins:asset-pipeline-grails' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.zaxxer:HikariCP' diff --git a/plugin-ldap/examples/custom-user-details-context-mapper/grails-app/conf/application.groovy b/plugin-ldap/examples/custom-user-details-context-mapper/grails-app/conf/application.groovy index 4d95b4d8e..89c706d45 100644 --- a/plugin-ldap/examples/custom-user-details-context-mapper/grails-app/conf/application.groovy +++ b/plugin-ldap/examples/custom-user-details-context-mapper/grails-app/conf/application.groovy @@ -47,12 +47,11 @@ grails { authorityJoinClassName = 'com.test.UserRole' } - // http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/ ldap { context { - managerDn = 'cn=read-only-admin,dc=example,dc=com' - managerPassword = 'password' - server = 'ldap://ldap.forumsys.com:389/' //'ldap://[ip]:[port]/' + managerDn = 'cn=admin,dc=example,dc=com' + managerPassword = 'secret' + server = System.getProperty('grails.test.ldap.url') } authorities { ignorePartialResultException = true @@ -61,7 +60,7 @@ grails { defaultRole = 'ROLE_USER' } search { - base = 'dc=example,dc=com' + base = 'ou=people,dc=example,dc=com' } } } diff --git a/plugin-ldap/examples/custom-user-details-context-mapper/grails-app/init/com/test/Application.groovy b/plugin-ldap/examples/custom-user-details-context-mapper/grails-app/init/com/test/Application.groovy index 56e851526..f43276bd9 100644 --- a/plugin-ldap/examples/custom-user-details-context-mapper/grails-app/init/com/test/Application.groovy +++ b/plugin-ldap/examples/custom-user-details-context-mapper/grails-app/init/com/test/Application.groovy @@ -19,6 +19,11 @@ package com.test +import com.unboundid.ldap.listener.InMemoryDirectoryServer +import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig +import com.unboundid.ldap.listener.InMemoryListenerConfig +import com.unboundid.ldap.sdk.Attribute +import com.unboundid.ldap.sdk.Entry import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration @@ -26,7 +31,58 @@ import groovy.transform.CompileStatic @CompileStatic class Application extends GrailsAutoConfiguration { + static InMemoryDirectoryServer directoryServer static void main(String[] args) { + InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig('dc=example,dc=com') + config.addAdditionalBindCredentials('cn=admin,dc=example,dc=com', 'secret') + config.setListenerConfigs( + InMemoryListenerConfig.createLDAPConfig( + 'default', + null, + 0, + null, + false, + false + ) + ) + + directoryServer = new InMemoryDirectoryServer(config) + Entry base = new Entry( + "dc=example,dc=com", + new Attribute("objectClass", "top", "domain"), + new Attribute("dc", "example")) + directoryServer.add(base) + + Entry people = new Entry( + "ou=people,dc=example,dc=com", + new Attribute("objectClass", "top", "organizationalUnit"), + new Attribute("ou", "people")); + directoryServer.add(people) + + Entry jane = new Entry( + "uid=jane,ou=people,dc=example,dc=com", + new Attribute("objectClass", "inetOrgPerson"), + new Attribute("uid", "jane"), + new Attribute("cn", "Jane Doe"), + new Attribute("sn", "Doe"), + new Attribute("mail", "jane@example.com"), + new Attribute("telephoneNumber", "+1 555 111 2222"), + new Attribute("userPassword", "password") + ) + directoryServer.add(jane) + + directoryServer.startListening() + + System.setProperty('grails.test.ldap.url', "ldap://localhost:${directoryServer.getListenPort()}" as String) + GrailsApp.run(Application, args) } + + @Override + void onShutdown(Map event) { + if(directoryServer) { + directoryServer.close() + directoryServer = null + } + } } \ No newline at end of file diff --git a/plugin-ldap/examples/custom-user-details-context-mapper/src/integration-test/groovy/com/test/CustomUserDetailsContextMapperFunctionalSpec.groovy b/plugin-ldap/examples/custom-user-details-context-mapper/src/integration-test/groovy/com/test/CustomUserDetailsContextMapperFunctionalSpec.groovy index 9a196db32..26a8a141a 100644 --- a/plugin-ldap/examples/custom-user-details-context-mapper/src/integration-test/groovy/com/test/CustomUserDetailsContextMapperFunctionalSpec.groovy +++ b/plugin-ldap/examples/custom-user-details-context-mapper/src/integration-test/groovy/com/test/CustomUserDetailsContextMapperFunctionalSpec.groovy @@ -19,6 +19,7 @@ package com.test +import grails.plugin.geb.ContainerGebConfiguration import grails.testing.mixin.integration.Integration import pages.IndexPage import pages.SecureSuperuserPage @@ -49,13 +50,13 @@ class CustomUserDetailsContextMapperFunctionalSpec extends AbstractSecurityFunct assertContentContains 'Please Login' when: - login 'galileo', 'password' + login 'jane', 'password' then: at SecureUserPage and: - assertContentContains('galileo@ldap.forumsys.com') + assertContentContains('jane@example.com') when: logout() diff --git a/plugin-ldap/examples/custom-user-details-context-mapper/src/integration-test/groovy/pages/LoginPage.groovy b/plugin-ldap/examples/custom-user-details-context-mapper/src/integration-test/groovy/pages/LoginPage.groovy index 1120f3004..4b1ec44b7 100644 --- a/plugin-ldap/examples/custom-user-details-context-mapper/src/integration-test/groovy/pages/LoginPage.groovy +++ b/plugin-ldap/examples/custom-user-details-context-mapper/src/integration-test/groovy/pages/LoginPage.groovy @@ -25,7 +25,7 @@ class LoginPage extends Page { static url = 'login/auth' - static at = { title == 'Login' } + static at = { waitFor { title == 'Login' } } static content = { loginForm { $('form') } diff --git a/plugin-ldap/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy b/plugin-ldap/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy index 1120f3004..4b1ec44b7 100644 --- a/plugin-ldap/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy +++ b/plugin-ldap/examples/functional-test-app/src/integration-test/groovy/pages/LoginPage.groovy @@ -25,7 +25,7 @@ class LoginPage extends Page { static url = 'login/auth' - static at = { title == 'Login' } + static at = { waitFor { title == 'Login' } } static content = { loginForm { $('form') } diff --git a/plugin-ldap/examples/retrieve-db-roles/src/integration-test/groovy/pages/LoginPage.groovy b/plugin-ldap/examples/retrieve-db-roles/src/integration-test/groovy/pages/LoginPage.groovy index 1120f3004..4b1ec44b7 100644 --- a/plugin-ldap/examples/retrieve-db-roles/src/integration-test/groovy/pages/LoginPage.groovy +++ b/plugin-ldap/examples/retrieve-db-roles/src/integration-test/groovy/pages/LoginPage.groovy @@ -25,7 +25,7 @@ class LoginPage extends Page { static url = 'login/auth' - static at = { title == 'Login' } + static at = { waitFor { title == 'Login' } } static content = { loginForm { $('form') } diff --git a/plugin-ldap/examples/retrieve-group-roles/build.gradle b/plugin-ldap/examples/retrieve-group-roles/build.gradle index c2d15bb73..4e20919b3 100644 --- a/plugin-ldap/examples/retrieve-group-roles/build.gradle +++ b/plugin-ldap/examples/retrieve-group-roles/build.gradle @@ -36,6 +36,9 @@ dependencies { implementation 'org.webjars:bootstrap:4.1.3' implementation 'org.webjars:jquery:3.3.1' + // to not depend on an external ldap server + implementation "com.unboundid:unboundid-ldapsdk:$unboundidLdapSdk" + runtimeOnly 'com.bertramlabs.plugins:asset-pipeline-grails' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.zaxxer:HikariCP' diff --git a/plugin-ldap/examples/retrieve-group-roles/grails-app/conf/application.groovy b/plugin-ldap/examples/retrieve-group-roles/grails-app/conf/application.groovy index 88f2b131a..b819e4e4d 100644 --- a/plugin-ldap/examples/retrieve-group-roles/grails-app/conf/application.groovy +++ b/plugin-ldap/examples/retrieve-group-roles/grails-app/conf/application.groovy @@ -18,53 +18,52 @@ */ grails { - plugin { - springsecurity { - authority { - className = 'com.test.Role' - } - controllerAnnotations.staticRules = [ - [pattern: '/', access: 'permitAll'], - [pattern: '/logoff', access: 'permitAll'], - [pattern: '/error', access: 'permitAll'], - [pattern: '/index', access: 'permitAll'], - [pattern: '/index.gsp', access: 'permitAll'], - [pattern: '/shutdown', access: 'permitAll'], - [pattern: '/assets/**', access: 'permitAll'], - [pattern: '/secure/users', access: 'permitAll'], - [pattern: '/**/js/**', access: 'permitAll'], - [pattern: '/**/css/**', access: 'permitAll'], - [pattern: '/**/images/**', access: 'permitAll'], - [pattern: '/**/favicon.ico', access: 'permitAll'] - ] - password.algorithm = 'SHA-256' - rememberMe { - persistent = true - persistentToken.domainClassName = 'com.test.PersistentLogin' - } - userLookup { - userDomainClassName = 'com.test.User' - authorityJoinClassName = 'com.test.UserRole' - } + plugin { + springsecurity { + authority { + className = 'com.test.Role' + } + controllerAnnotations.staticRules = [ + [pattern: '/', access: 'permitAll'], + [pattern: '/logoff', access: 'permitAll'], + [pattern: '/error', access: 'permitAll'], + [pattern: '/index', access: 'permitAll'], + [pattern: '/index.gsp', access: 'permitAll'], + [pattern: '/shutdown', access: 'permitAll'], + [pattern: '/assets/**', access: 'permitAll'], + [pattern: '/secure/users', access: 'permitAll'], + [pattern: '/**/js/**', access: 'permitAll'], + [pattern: '/**/css/**', access: 'permitAll'], + [pattern: '/**/images/**', access: 'permitAll'], + [pattern: '/**/favicon.ico', access: 'permitAll'] + ] + password.algorithm = 'SHA-256' + rememberMe { + persistent = true + persistentToken.domainClassName = 'com.test.PersistentLogin' + } + userLookup { + userDomainClassName = 'com.test.User' + authorityJoinClassName = 'com.test.UserRole' + } - // http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/ - ldap { - context { - managerDn = 'cn=read-only-admin,dc=example,dc=com' - managerPassword = 'password' - server = 'ldap://ldap.forumsys.com:389/' //'ldap://[ip]:[port]/' - } - authorities { - ignorePartialResultException = true - retrieveGroupRoles = true - groupSearchBase='ou=mathematicians,dc=example,dc=com' - retrieveDatabaseRoles = true - defaultRole = 'ROLE_USER' - } - search { - base = 'dc=example,dc=com' - } - } - } - } + ldap { + context { + managerDn = 'cn=admin,dc=example,dc=com' + managerPassword = 'secret' + server = System.getProperty('grails.test.ldap.url') + } + authorities { + ignorePartialResultException = true + retrieveGroupRoles = true + groupSearchBase = 'ou=mathematicians,dc=example,dc=com' + retrieveDatabaseRoles = true + defaultRole = 'ROLE_USER' + } + search { + base = 'dc=example,dc=com' + } + } + } + } } \ No newline at end of file diff --git a/plugin-ldap/examples/retrieve-group-roles/grails-app/init/com/test/Application.groovy b/plugin-ldap/examples/retrieve-group-roles/grails-app/init/com/test/Application.groovy index 56e851526..0deae45af 100644 --- a/plugin-ldap/examples/retrieve-group-roles/grails-app/init/com/test/Application.groovy +++ b/plugin-ldap/examples/retrieve-group-roles/grails-app/init/com/test/Application.groovy @@ -19,6 +19,11 @@ package com.test +import com.unboundid.ldap.listener.InMemoryDirectoryServer +import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig +import com.unboundid.ldap.listener.InMemoryListenerConfig +import com.unboundid.ldap.sdk.Attribute +import com.unboundid.ldap.sdk.Entry import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration @@ -26,7 +31,113 @@ import groovy.transform.CompileStatic @CompileStatic class Application extends GrailsAutoConfiguration { + static InMemoryDirectoryServer directoryServer + static private Entry scientistsUnit + static void main(String[] args) { + InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig('dc=example,dc=com') + config.addAdditionalBindCredentials('cn=admin,dc=example,dc=com', 'secret') + config.setListenerConfigs( + InMemoryListenerConfig.createLDAPConfig( + 'default', + null, + 0, + null, + false, + false + ) + ) + + directoryServer = new InMemoryDirectoryServer(config) + Entry base = new Entry( + 'dc=example,dc=com', + new Attribute('objectClass', 'top', 'domain'), + new Attribute('dc', 'example')) + directoryServer.add(base) + + Entry people = new Entry( + 'ou=people,dc=example,dc=com', + new Attribute('objectClass', 'top', 'organizationalUnit'), + new Attribute('ou', 'people')); + directoryServer.add(people) + + def mathematiciansUnit = new Entry('ou=mathematicians,dc=example,dc=com', + new Attribute('objectClass', 'top', 'organizationalUnit'), + new Attribute('ou', 'mathematicians')) + directoryServer.add(mathematiciansUnit) + + scientistsUnit = new Entry('ou=scientists,dc=example,dc=com', + new Attribute('objectClass', 'top', 'organizationalUnit'), + new Attribute('ou', 'scientists')) + directoryServer.add(scientistsUnit) + + Entry jane = new Entry( + 'uid=jane,ou=people,dc=example,dc=com', + new Attribute('objectClass', 'inetOrgPerson'), + new Attribute('uid', 'jane'), + new Attribute('cn', 'Jane Doe'), + new Attribute('sn', 'Doe'), + new Attribute('mail', 'jane@example.com'), + new Attribute('telephoneNumber', '+1 555 111 2222'), + new Attribute('userPassword', 'password') + ) + directoryServer.add(jane) + + for (String uid : ['riemann', 'gauss', 'euler', 'euclid']) { + directoryServer.add(new Entry( + "uid=${uid},ou=mathematicians,dc=example,dc=com" as String, + new Attribute('objectClass', 'inetOrgPerson'), + new Attribute('uid', uid), + new Attribute('cn', uid.substring(0, 1).toUpperCase() + uid.substring(1)), + new Attribute('sn', uid.substring(0, 1).toUpperCase() + uid.substring(1)), + new Attribute('userPassword', 'password') + )) + } + + Entry mathGroup = new Entry( + 'cn=mathematicians,ou=mathematicians,dc=example,dc=com', + new Attribute('objectClass', 'top', 'groupOfUniqueNames'), + new Attribute('cn', 'mathematicians'), + new Attribute('uniqueMember', + 'uid=riemann,ou=mathematicians,dc=example,dc=com', + 'uid=gauss,ou=mathematicians,dc=example,dc=com', + 'uid=euler,ou=mathematicians,dc=example,dc=com', + 'uid=euclid,ou=mathematicians,dc=example,dc=com')) + directoryServer.add(mathGroup) + + for (String uid : ['einstein', 'newton', 'galieleo', 'tesla']) { + directoryServer.add(new Entry( + "uid=${uid},ou=scientists,dc=example,dc=com" as String, + new Attribute('objectClass', 'inetOrgPerson'), + new Attribute('uid', uid), + new Attribute('cn', uid.substring(0, 1).toUpperCase() + uid.substring(1)), + new Attribute('sn', uid.substring(0, 1).toUpperCase() + uid.substring(1)), + new Attribute('userPassword', 'password') + )) + } + Entry scientistGroup = new Entry( + 'cn=scientists,ou=scientists,dc=example,dc=com', + new Attribute('objectClass', 'top', 'groupOfUniqueNames'), + new Attribute('cn', 'scientists'), + new Attribute('uniqueMember', + 'uid=einstein,ou=scientists,dc=example,dc=com', + 'uid=newton,ou=scientists,dc=example,dc=com', + 'uid=galieleo,ou=scientists,dc=example,dc=com', + 'uid=tesla,ou=scientists,dc=example,dc=com')) + directoryServer.add(scientistGroup) + + directoryServer.startListening() + + System.setProperty('grails.test.ldap.url', "ldap://localhost:${directoryServer.getListenPort()}" as String) + GrailsApp.run(Application, args) } + + @Override + void onShutdown(Map event) { + if (directoryServer) { + directoryServer.close() + directoryServer = null + } + } } \ No newline at end of file diff --git a/plugin-ldap/examples/retrieve-group-roles/src/integration-test/groovy/pages/LoginPage.groovy b/plugin-ldap/examples/retrieve-group-roles/src/integration-test/groovy/pages/LoginPage.groovy index 1120f3004..4b1ec44b7 100644 --- a/plugin-ldap/examples/retrieve-group-roles/src/integration-test/groovy/pages/LoginPage.groovy +++ b/plugin-ldap/examples/retrieve-group-roles/src/integration-test/groovy/pages/LoginPage.groovy @@ -25,7 +25,7 @@ class LoginPage extends Page { static url = 'login/auth' - static at = { title == 'Login' } + static at = { waitFor { title == 'Login' } } static content = { loginForm { $('form') } diff --git a/plugin-rest/spring-security-rest-redis/build.gradle b/plugin-rest/spring-security-rest-redis/build.gradle index 4235279ab..732488581 100644 --- a/plugin-rest/spring-security-rest-redis/build.gradle +++ b/plugin-rest/spring-security-rest-redis/build.gradle @@ -43,8 +43,7 @@ dependencies { // api: TokenNotFoundException, TokenStorageService // impl: SecureRandomTokenGenerator } - api "org.grails.plugins:grails-redis:$grailsRedisVersion", { - // TODO: Not yet converted to apache coords + api "org.apache.grails:grails-redis:$grailsRedisVersion", { // api: RedisService } api 'org.springframework:spring-core', { diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidCreatePage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidCreatePage.groovy index 7c2cdee42..e3c10c3de 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidCreatePage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidCreatePage.groovy @@ -27,6 +27,7 @@ class AclSidCreatePage extends CreatePage { static url = 'aclSid/create' static typeName = { 'AclSid' } + static at = { waitFor { title == 'Create AclSid' } } static content = { sid { $(name: 'sid').module(TextInput) } principal { $(name: 'principal').module(Checkbox) } diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidEditPage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidEditPage.groovy index 524edb699..82ec53647 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidEditPage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidEditPage.groovy @@ -27,6 +27,7 @@ class AclSidEditPage extends EditPage { static url = 'aclSid/edit' static typeName = { 'AclSid' } + static at = { waitFor { title == 'Edit AclSid' } } static content = { sid { $(name: 'sid').module(TextInput) } principal { $(name: 'principal').module(Checkbox) } diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidSearchPage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidSearchPage.groovy index e9fef5ba7..228bf3de1 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidSearchPage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/aclSid/AclSidSearchPage.groovy @@ -27,6 +27,7 @@ class AclSidSearchPage extends SearchPage { static url = 'aclSid/search' static typeName = { 'AclSid' } + static at = { waitFor { title == 'AclSid Search' } } static content = { sid { $(name: 'sid').module(TextInput) } principal { $(name: 'principal').module(RadioButtons) } diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/register/ForgotPasswordPage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/register/ForgotPasswordPage.groovy index b56020a1c..a305438fa 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/register/ForgotPasswordPage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/register/ForgotPasswordPage.groovy @@ -25,7 +25,7 @@ import page.AbstractSecurityPage class ForgotPasswordPage extends AbstractSecurityPage { static url = 'register/forgotPassword' - static at = { title == 'Forgot Password' } + static at = { waitFor { title == 'Forgot Password' } } static content = { form { $('forgotPasswordForm') } username { $(name: 'username').module(TextInput) } diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/register/SecurityQuestionsPage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/register/SecurityQuestionsPage.groovy index 4d78a4949..9edda1478 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/register/SecurityQuestionsPage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/register/SecurityQuestionsPage.groovy @@ -25,7 +25,7 @@ import page.AbstractSecurityPage class SecurityQuestionsPage extends AbstractSecurityPage { static url = 'register/securityQuestions' - static at = { title == 'Security Questions' } + static at = { waitFor { title == 'Security Questions' } } static content = { form { $('securityQuestionsForm') } question1 { $('#myAnswer1').module(TextInput) } diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/registrationCode/RegistrationCodeSearchPage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/registrationCode/RegistrationCodeSearchPage.groovy index dc2c10a03..fe059ad43 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/registrationCode/RegistrationCodeSearchPage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/registrationCode/RegistrationCodeSearchPage.groovy @@ -26,6 +26,7 @@ class RegistrationCodeSearchPage extends SearchPage { static url = 'registrationCode/search' static typeName = { 'Registration Code' } + static at = { waitFor { title == 'Registration Code Search' } } static content = { token { $(name: 'token').module(TextInput) } username { $('#username').module(TextInput) } diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/requestmap/RequestmapSearchPage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/requestmap/RequestmapSearchPage.groovy index 2bdcc1fda..4c123e128 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/requestmap/RequestmapSearchPage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/requestmap/RequestmapSearchPage.groovy @@ -26,6 +26,7 @@ class RequestmapSearchPage extends SearchPage { static url = 'requestmap/search' static typeName = { 'Requestmap' } + static at = { waitFor { title == 'Requestmap Search' } } static content = { configAttribute { $(name: 'configAttribute').module(TextInput) } urlPattern { $(name: 'url').module(TextInput) } diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/role/RoleSearchPage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/role/RoleSearchPage.groovy index 89014002e..02f7e08da 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/role/RoleSearchPage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/role/RoleSearchPage.groovy @@ -26,6 +26,7 @@ class RoleSearchPage extends SearchPage { static url = 'role/search' static typeName = { 'Role' } + static at = { waitFor { title == 'Role Search' } } static content = { authority { $(name: 'authority').module(TextInput) } } diff --git a/plugin-ui/examples/extended/src/integration-test/groovy/page/user/UserSearchPage.groovy b/plugin-ui/examples/extended/src/integration-test/groovy/page/user/UserSearchPage.groovy index 0847fa8b7..a20179383 100644 --- a/plugin-ui/examples/extended/src/integration-test/groovy/page/user/UserSearchPage.groovy +++ b/plugin-ui/examples/extended/src/integration-test/groovy/page/user/UserSearchPage.groovy @@ -27,6 +27,7 @@ class UserSearchPage extends SearchPage { static url = 'user/search' static typeName = { 'User' } + static at = { waitFor { title == 'User Search' } } static content = { username { $('#username').module(TextInput) } enabled { $(name: 'enabled').module(RadioButtons) } diff --git a/plugin-ui/examples/simple/grails-app/views/securityInfo/config.gsp b/plugin-ui/examples/simple/grails-app/views/securityInfo/config.gsp index 59db6e1a4..a1db1d20c 100644 --- a/plugin-ui/examples/simple/grails-app/views/securityInfo/config.gsp +++ b/plugin-ui/examples/simple/grails-app/views/securityInfo/config.gsp @@ -53,7 +53,7 @@ if (value instanceof Class) { - + $('#config').DataTable(); diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/CreatePage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/CreatePage.groovy index 266899a16..9db039dcb 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/CreatePage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/CreatePage.groovy @@ -21,7 +21,7 @@ package page abstract class CreatePage extends AbstractSecurityPage { - static at = { title == "Create ${typeName()}" } + static at = { waitFor { title == "Create ${typeName()}" } } static content = { form { $('createForm') } submitBtn { $('a', id: 'create') } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/EditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/EditPage.groovy index 3ada96551..e80ec1b71 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/EditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/EditPage.groovy @@ -21,7 +21,7 @@ package page abstract class EditPage extends AbstractSecurityPage { - static at = { title == "Edit ${typeName()}" } + static at = { waitFor { title == "Edit ${typeName()}" } } static content = { form { $('editForm') } submitBtn { $('a', id: 'update') } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/SearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/SearchPage.groovy index 0986571d2..7887b334d 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/SearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/SearchPage.groovy @@ -21,28 +21,28 @@ package page abstract class SearchPage extends AbstractSecurityPage { - static at = { title == "${typeName()} Search" } - static atCheckWaiting = true - static content = { - form { $('search') } - submitBtn { $('a', id: 'searchButton') } - } + static at = { waitFor { title == "${typeName()} Search" } } + static atCheckWaiting = true + static content = { + form { $('search') } + submitBtn { $('a', id: 'searchButton') } + } - boolean assertNoResults() { - assertContentContains('No results') - assertContentDoesNotContain('Showing') - true - } + boolean assertNoResults() { + assertContentContains('No results') + assertContentDoesNotContain('Showing') + true + } - boolean assertNotSearched() { - assertContentContains('Search') - assertContentDoesNotContain('No results') - assertContentDoesNotContain('Showing') - true - } + boolean assertNotSearched() { + assertContentContains('Search') + assertContentDoesNotContain('No results') + assertContentDoesNotContain('Showing') + true + } - boolean assertResults(int start, int end, int total) { - assertContentContains("Showing $start through $end out of ${total}.") - true - } + boolean assertResults(int start, int end, int total) { + assertContentContains("Showing $start through $end out of ${total}.") + true + } } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassCreatePage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassCreatePage.groovy index 6afd418a8..7c24ee065 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassCreatePage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassCreatePage.groovy @@ -26,6 +26,7 @@ class AclClassCreatePage extends CreatePage { static url = 'aclClass/create' static typeName = { 'AclClass' } + static at = { waitFor { title == 'Create AclClass' } } static content = { className { $(name: 'className').module(TextInput) } } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassEditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassEditPage.groovy index 49ca009d1..d68d37662 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassEditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassEditPage.groovy @@ -26,6 +26,7 @@ class AclClassEditPage extends EditPage { static url = 'aclClass/edit' static typeName = { 'AclClass' } + static at = { waitFor { title == 'Edit AclClass' } } static content = { className { $(name: 'className').module(TextInput) } } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassSearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassSearchPage.groovy index d1770eb9e..dddce9ad7 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassSearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclClass/AclClassSearchPage.groovy @@ -26,6 +26,7 @@ class AclClassSearchPage extends SearchPage { static url = 'aclClass/search' static typeName = { 'AclClass' } + static at = { waitFor { title == 'AclClass Search' } } static content = { className { $(name: 'className').module(TextInput) } } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntryCreatePage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntryCreatePage.groovy index ecb818813..2b5cb7297 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntryCreatePage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntryCreatePage.groovy @@ -28,6 +28,7 @@ class AclEntryCreatePage extends CreatePage { static url = 'aclEntry/create' static typeName = { 'AclEntry' } + static at = { waitFor { title == 'Create AclEntry' } } static content = { aclObjectIdentityId { $(name: 'aclObjectIdentity.id').module(TextInput) } aceOrder { $(name: 'aceOrder').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntryEditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntryEditPage.groovy index 9ea8746d8..7c4faf09a 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntryEditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntryEditPage.groovy @@ -28,6 +28,7 @@ class AclEntryEditPage extends EditPage { static url = 'aclEntry/edit' static typeName = { 'AclEntry' } + static at = { waitFor { title == 'Edit AclEntry' } } static content = { aclObjectIdentityId { $(name: 'aclObjectIdentity.id').module(TextInput) } aceOrder { $(name: 'aceOrder').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntrySearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntrySearchPage.groovy index a1b91033a..1c68b0777 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntrySearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclEntry/AclEntrySearchPage.groovy @@ -26,6 +26,7 @@ class AclEntrySearchPage extends SearchPage { static url = 'aclEntry/search' static typeName = { 'AclEntry' } + static at = { waitFor { title == 'AclEntry Search' } } static content = { aclObjectIdentity { $(name: 'aclObjectIdentity.id').module(TextInput) } aceOrder { $(name: 'aceOrder').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentityCreatePage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentityCreatePage.groovy index c77e7087e..ac9e1b137 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentityCreatePage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentityCreatePage.groovy @@ -27,6 +27,7 @@ class AclObjectIdentityCreatePage extends CreatePage { static url = 'aclObjectIdentity/create' static typeName = { 'AclObjectIdentity' } + static at = { waitFor { title == 'Create AclObjectIdentity' } } static content = { aclClass { $(name: 'aclClass.id').module(Select) } objectId { $(name: 'objectId').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentityEditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentityEditPage.groovy index 671179ed0..009187e6b 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentityEditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentityEditPage.groovy @@ -26,6 +26,7 @@ class AclObjectIdentityEditPage extends EditPage { static url = 'aclObjectIdentity/edit' static typeName = { 'AclObjectIdentity' } + static at = { waitFor { title == 'Edit AclObjectIdentity' } } static content = { objectId { $(name: 'objectId').module(TextInput) } } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentitySearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentitySearchPage.groovy index c280e4933..2e6641c79 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentitySearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclObjectIdentity/AclObjectIdentitySearchPage.groovy @@ -27,6 +27,7 @@ class AclObjectIdentitySearchPage extends SearchPage { static url = 'aclObjectIdentity/search' static typeName = { 'AclObjectIdentity' } + static at = { waitFor { title == 'AclObjectIdentity Search' } } static content = { aclClass { $(name: 'aclClass.id').module(Select) } objectId { $(name: 'objectId').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidCreatePage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidCreatePage.groovy index 7c2cdee42..e3c10c3de 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidCreatePage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidCreatePage.groovy @@ -27,6 +27,7 @@ class AclSidCreatePage extends CreatePage { static url = 'aclSid/create' static typeName = { 'AclSid' } + static at = { waitFor { title == 'Create AclSid' } } static content = { sid { $(name: 'sid').module(TextInput) } principal { $(name: 'principal').module(Checkbox) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidEditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidEditPage.groovy index 524edb699..82ec53647 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidEditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidEditPage.groovy @@ -27,6 +27,7 @@ class AclSidEditPage extends EditPage { static url = 'aclSid/edit' static typeName = { 'AclSid' } + static at = { waitFor { title == 'Edit AclSid' } } static content = { sid { $(name: 'sid').module(TextInput) } principal { $(name: 'principal').module(Checkbox) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidSearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidSearchPage.groovy index e9fef5ba7..228bf3de1 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidSearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/aclSid/AclSidSearchPage.groovy @@ -27,6 +27,7 @@ class AclSidSearchPage extends SearchPage { static url = 'aclSid/search' static typeName = { 'AclSid' } + static at = { waitFor { title == 'AclSid Search' } } static content = { sid { $(name: 'sid').module(TextInput) } principal { $(name: 'principal').module(RadioButtons) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/persistentLogin/PersistentLoginSearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/persistentLogin/PersistentLoginSearchPage.groovy index 562b12a8b..7bdd4fe74 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/persistentLogin/PersistentLoginSearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/persistentLogin/PersistentLoginSearchPage.groovy @@ -26,6 +26,7 @@ class PersistentLoginSearchPage extends SearchPage { static url = 'persistentLogin/search' static typeName = { 'PersistentLogin' } + static at = { waitFor { title == 'PersistentLogin Search' } } static content = { series { $(name: 'series').module(TextInput) } token { $(name: 'token').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/register/ForgotPasswordPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/register/ForgotPasswordPage.groovy index b56020a1c..a305438fa 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/register/ForgotPasswordPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/register/ForgotPasswordPage.groovy @@ -25,7 +25,7 @@ import page.AbstractSecurityPage class ForgotPasswordPage extends AbstractSecurityPage { static url = 'register/forgotPassword' - static at = { title == 'Forgot Password' } + static at = { waitFor { title == 'Forgot Password' } } static content = { form { $('forgotPasswordForm') } username { $(name: 'username').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/register/RegisterPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/register/RegisterPage.groovy index cb9ff69af..005a22ca6 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/register/RegisterPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/register/RegisterPage.groovy @@ -26,7 +26,11 @@ import page.AbstractSecurityPage class RegisterPage extends AbstractSecurityPage { static url = 'register' - static at = { title == 'Register' } + static at = { + waitFor { + title == 'Register' + } + } static content = { form { $('registerForm') } username { $(name: 'username').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/register/ResetPasswordPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/register/ResetPasswordPage.groovy index 3a366f306..14d0f7d99 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/register/ResetPasswordPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/register/ResetPasswordPage.groovy @@ -25,7 +25,7 @@ import page.AbstractSecurityPage class ResetPasswordPage extends AbstractSecurityPage { static url = 'register/resetPassword' - static at = { title == 'Reset Password' } + static at = { waitFor { title == 'Reset Password' } } static content = { form { $('resetPasswordForm') } password { $('#password').module(PasswordInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/registrationCode/RegistrationCodeEditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/registrationCode/RegistrationCodeEditPage.groovy index 4976aa738..a61ee87c0 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/registrationCode/RegistrationCodeEditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/registrationCode/RegistrationCodeEditPage.groovy @@ -26,6 +26,7 @@ class RegistrationCodeEditPage extends EditPage { static url = 'registrationCode/edit' static typeName = { 'RegistrationCode' } + static at = { waitFor { title == 'Edit RegistrationCode' } } static content = { token { $(name: 'token').module(TextInput) } username { $('#username').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/registrationCode/RegistrationCodeSearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/registrationCode/RegistrationCodeSearchPage.groovy index dc2c10a03..fe059ad43 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/registrationCode/RegistrationCodeSearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/registrationCode/RegistrationCodeSearchPage.groovy @@ -26,6 +26,7 @@ class RegistrationCodeSearchPage extends SearchPage { static url = 'registrationCode/search' static typeName = { 'Registration Code' } + static at = { waitFor { title == 'Registration Code Search' } } static content = { token { $(name: 'token').module(TextInput) } username { $('#username').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapCreatePage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapCreatePage.groovy index 7e46436a8..e7423b0c6 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapCreatePage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapCreatePage.groovy @@ -26,6 +26,7 @@ class RequestmapCreatePage extends CreatePage { static url = 'requestmap/create' static typeName = { 'Requestmap' } + static at = { waitFor { title == 'Create Requestmap' } } static content = { configAttribute { $(name: 'configAttribute').module(TextInput) } urlPattern { $(name: 'url').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapEditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapEditPage.groovy index e66c1ba84..6e7390053 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapEditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapEditPage.groovy @@ -26,6 +26,7 @@ class RequestmapEditPage extends EditPage { static url = 'requestmap/edit' static typeName = { 'Requestmap' } + static at = { waitFor { title == 'Edit Requestmap' } } static content = { configAttribute { $(name: 'configAttribute').module(TextInput) } urlPattern { $(name: 'url').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapSearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapSearchPage.groovy index 2bdcc1fda..4c123e128 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapSearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/requestmap/RequestmapSearchPage.groovy @@ -26,6 +26,7 @@ class RequestmapSearchPage extends SearchPage { static url = 'requestmap/search' static typeName = { 'Requestmap' } + static at = { waitFor { title == 'Requestmap Search' } } static content = { configAttribute { $(name: 'configAttribute').module(TextInput) } urlPattern { $(name: 'url').module(TextInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleCreatePage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleCreatePage.groovy index d21170d49..4afd3f0d8 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleCreatePage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleCreatePage.groovy @@ -26,6 +26,7 @@ class RoleCreatePage extends CreatePage { static url = 'role/create' static typeName = { 'Role' } + static at = { waitFor { title == 'Create Role' } } static content = { authority { $(name: 'authority').module(TextInput) } } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleEditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleEditPage.groovy index 0bd9f3a18..5b5002da1 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleEditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleEditPage.groovy @@ -26,6 +26,7 @@ class RoleEditPage extends EditPage { static url = 'role/edit' static typeName = { 'Role' } + static at = { waitFor { title == 'Edit Role' } } static content = { authority { $(name: 'authority').module(TextInput) } } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleSearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleSearchPage.groovy index 89014002e..02f7e08da 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleSearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/role/RoleSearchPage.groovy @@ -26,6 +26,7 @@ class RoleSearchPage extends SearchPage { static url = 'role/search' static typeName = { 'Role' } + static at = { waitFor { title == 'Role Search' } } static content = { authority { $(name: 'authority').module(TextInput) } } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserCreatePage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserCreatePage.groovy index d6b987ea8..182a34bd7 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserCreatePage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserCreatePage.groovy @@ -28,6 +28,7 @@ class UserCreatePage extends CreatePage { static url = 'user/create' static typeName = { 'User' } + static at = { waitFor { title == 'Create User' } } static content = { username { $('#username').module(TextInput) } password { $('#password').module(PasswordInput) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserEditPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserEditPage.groovy index 0e3e7a905..a4de6f292 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserEditPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserEditPage.groovy @@ -28,6 +28,7 @@ class UserEditPage extends EditPage { static url = 'user/edit' static typeName = { 'User' } + static at = { waitFor { title == 'Edit User' } } static content = { username { $('#username').module(TextInput) } enabled { $(name: 'enabled').module(Checkbox) } diff --git a/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserSearchPage.groovy b/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserSearchPage.groovy index 0847fa8b7..a20179383 100644 --- a/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserSearchPage.groovy +++ b/plugin-ui/examples/simple/src/integration-test/groovy/page/user/UserSearchPage.groovy @@ -27,6 +27,7 @@ class UserSearchPage extends SearchPage { static url = 'user/search' static typeName = { 'User' } + static at = { waitFor { title == 'User Search' } } static content = { username { $('#username').module(TextInput) } enabled { $(name: 'enabled').module(RadioButtons) } diff --git a/plugin-ui/plugin/grails-app/views/securityInfo/config.gsp b/plugin-ui/plugin/grails-app/views/securityInfo/config.gsp index 59db6e1a4..a1db1d20c 100644 --- a/plugin-ui/plugin/grails-app/views/securityInfo/config.gsp +++ b/plugin-ui/plugin/grails-app/views/securityInfo/config.gsp @@ -53,7 +53,7 @@ if (value instanceof Class) { - + $('#config').DataTable(); diff --git a/settings.gradle b/settings.gradle index 8eec16799..bdcb11d2c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -50,14 +50,6 @@ buildCache { } } -gradle.beforeProject { - // LDAP examples cannot run in parallel, - // because they are connecting to live LDAP server (presumably with rate limiting) - if (it.name.startsWith('ldap-examples-')) { - gradle.startParameter.parallelProjectExecutionEnabled = false - } -} - rootProject.name = 'grails-spring-security.ROOT' include 'acl-plugin'