diff --git a/.gitignore b/.gitignore index 160d8bdd0..f8092a9dd 100644 --- a/.gitignore +++ b/.gitignore @@ -420,3 +420,4 @@ beacon/spring/out /testbed/authentication/shibui/saml-signing-cert.pem /testbed/authentication/shibui/samlKeystore.jks /testbed/authentication/shibui/sp-metadata.xml +/backend/src/main/resources/application.yml diff --git a/backend/build.gradle b/backend/build.gradle index 506df6553..15a841f2f 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -37,7 +37,7 @@ configurations.all { eachDependency { details -> if (details.requested.group == 'org.seleniumhq.selenium' && details.requested.name != 'htmlunit-driver') { - details.useVersion '3.141.59' + details.useVersion '4.7.0' } } } @@ -91,6 +91,20 @@ processResources.dependsOn(':ui:npm_run_buildProd') jar { enabled = true + dependsOn 'copyApplicationYAML' +} + +task copyApplicationYAML(type: Copy) { + if ( "${project.'use.release.app.yml'}".toBoolean() ) { + from 'src/main/app-resources/release.yml' + into 'src/main/resources' + rename { 'application.yml' } + } + else { + from 'src/main/app-resources/default.yml' + into 'src/main/resources' + rename { 'application.yml' } + } } //Integration of the frontend and backend into the build to have all of the UI resources available in the app's executable war @@ -206,7 +220,7 @@ dependencies { integrationTestCompile sourceSets.main.output integrationTestCompile configurations.compile integrationTestCompile 'com.saucelabs:sebuilder-interpreter:1.0.6' - integrationTestCompile 'jp.vmi:selenese-runner-java:3.20.0' + integrationTestCompile 'jp.vmi:selenese-runner-java:4.2.0' integrationTestCompile "org.springframework.boot:spring-boot-starter-test:${project.'springbootVersion'}" integrationTestCompile "org.springframework.security:spring-security-test:${project.'springSecurityVersion'}" integrationTestCompile platform("org.spockframework:spock-bom:2.1-groovy-3.0") @@ -397,4 +411,4 @@ dockerRun { daemonize true command '--spring.profiles.include=very-dangerous,dev', '--shibui.default-password={noop}password' clean true -} \ No newline at end of file +} diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy index 79a00d44d..3ccedd5d8 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/EntityDescriptorEnversVersioningTests.groovy @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.repository.envers import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.AssertionConsumerService import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute @@ -63,7 +64,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTes * Testing entity descriptor envers versioning */ @DataJpaTest -@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, SearchConfiguration, TestConfiguration]) +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, SearchConfiguration, TestConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") class EntityDescriptorEnversVersioningTests extends Specification { diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy index 6df7da461..ea49d61c0 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataFilterEnversVersioningTests.groovy @@ -8,6 +8,7 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.CustomPropertiesConf import edu.internet2.tier.shibboleth.admin.ui.configuration.EntitiesVersioningConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityRoleWhiteListFilter @@ -38,7 +39,7 @@ import spock.lang.Specification * Testing metadata resolver envers versioning with metadata filters */ @DataJpaTest -@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, SearchConfiguration, TestConfiguration, EntitiesVersioningConfiguration]) +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, SearchConfiguration, TestConfiguration, EntitiesVersioningConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") class MetadataFilterEnversVersioningTests extends Specification { @@ -334,4 +335,4 @@ class MetadataFilterEnversVersioningTests extends Specification { mrv1.metadataFilters.size() == 1 mrv2.metadataFilters.size() == 0 } -} +} \ No newline at end of file diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy index f8ac3b431..358020283 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEntityBasicEnversVersioningTests.groovy @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.repository.envers import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget @@ -25,7 +26,7 @@ import javax.persistence.EntityManager * Testing metadata resolvers basic versioning by envers is functioning. */ @DataJpaTest -@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration]) +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") class MetadataResolverEntityBasicEnversVersioningTests extends Specification { @@ -101,4 +102,4 @@ class MetadataResolverEntityBasicEnversVersioningTests extends Specification { txMgr.commit(txStatus) entity } -} +} \ No newline at end of file diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy index d828b8e19..e6d032e0b 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/repository/envers/MetadataResolverEnversVersioningTests.groovy @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.repository.envers import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ClasspathMetadataResource import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver @@ -36,7 +37,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.repository.envers.EnversTes * Testing metadata resolver envers versioning */ @DataJpaTest -@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, SearchConfiguration, TestConfiguration]) +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, SearchConfiguration, TestConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") class MetadataResolverEnversVersioningTests extends Specification { diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy index 76ca684e8..1ce0ba1b2 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversEntityDescriptorVersionServiceTests.groovy @@ -4,6 +4,7 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfigurat import edu.internet2.tier.shibboleth.admin.ui.configuration.EntitiesVersioningConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound @@ -24,7 +25,7 @@ import java.time.ZonedDateTime @DataJpaTest -@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration]) +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") @EnableJpaAuditing diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversMetadataResolverVersionServiceTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversMetadataResolverVersionServiceTests.groovy index 97b37f10e..9e1db2e6d 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversMetadataResolverVersionServiceTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversMetadataResolverVersionServiceTests.groovy @@ -5,6 +5,7 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfigurat import edu.internet2.tier.shibboleth.admin.ui.configuration.EntitiesVersioningConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.FilesystemMetadataResolver @@ -27,7 +28,7 @@ import java.time.ZonedDateTime @DataJpaTest -@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration]) +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") @EnableJpaAuditing @@ -134,4 +135,4 @@ class EnversMetadataResolverVersionServiceTests extends Specification { then: !nonexitentMr } -} +} \ No newline at end of file diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversVersioningMetadataTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversVersioningMetadataTests.groovy index 3bc1a2f98..cca4fb6de 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversVersioningMetadataTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/service/envers/EnversVersioningMetadataTests.groovy @@ -4,6 +4,7 @@ import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfigurat import edu.internet2.tier.shibboleth.admin.ui.configuration.EntitiesVersioningConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver @@ -24,7 +25,7 @@ import org.springframework.transaction.PlatformTransactionManager import spock.lang.Specification @DataJpaTest -@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration]) +@ContextConfiguration(classes = [CoreShibUiConfiguration, InternationalizationConfiguration, TestConfiguration, SearchConfiguration, EntitiesVersioningConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") @EnableJpaAuditing @@ -124,4 +125,4 @@ class EnversVersioningMetadataTests extends Specification { mrV2.isCurrent() edV2.isCurrent() } -} +} \ No newline at end of file diff --git a/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy b/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy index 1455db029..dc35f76b1 100644 --- a/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy +++ b/backend/src/integration/groovy/edu/internet2/tier/shibboleth/admin/ui/SeleniumSIDETest.groovy @@ -88,6 +88,10 @@ class SeleniumSIDETest extends Specification { if (System.properties.getProperty('webdriver.headless')) { it.addCliArgs('--headless') } + if (System.properties.getProperty('webdriver.set.speed')) { + println("WOO! Setting speed: " + "-set-speed=${System.properties.getProperty('webdriver.set.speed')}") + it.addCliArgs("-set-speed=${System.properties.getProperty('webdriver.set.speed')}") + } it } def runner = new Runner() @@ -107,7 +111,7 @@ class SeleniumSIDETest extends Specification { where: name | file - 'SHIBUI-1364: Compare FBHTTPMP with filters' | '/SHIBUI-1364-1.side' +/* 'SHIBUI-1364: Compare FBHTTPMP with filters' | '/SHIBUI-1364-1.side' 'SHIBUI-1364: Compare FSMP' | '/SHIBUI-1364-2.side' 'SHIBUI-1364: Compare LDMP' | '/SHIBUI-1364-3.side' 'SHIBUI-1364: Compare DHTTPMP with filters' | '/SHIBUI-1364-4.side' @@ -122,14 +126,14 @@ class SeleniumSIDETest extends Specification { 'SHIBUI-1335: Verify File Backed HTTP Metadata Provider Filters' | '/SHIBUI-1335-1.side' 'SHIBUI-1335: Verify Filesystem Metadata Provider' | '/SHIBUI-1335-2.side' 'SHIBUI-1335: Verify Local Dynamic Metadata Provider' | '/SHIBUI-1335-3.side' - 'SHIBUI-1335: Verify Dynamic HTTP Metadata Provider Filters' | '/SHIBUI-1335-4.side' + 'SHIBUI-1335: Verify Dynamic HTTP Metadata Provider Filters' | '/SHIBUI-1335-4.side'*/ 'SHIBUI-1361: Verify dates display in proper format' | '/SHIBUI-1361.side' // Note that this script WILL NOT PASS in the Selenium IDE due to it thinking there is a syntax error where there is none. 'SHIBUI-1385: Restore a metadata source version' | '/SHIBUI-1385-1.side' 'SHIBUI-1385: Restore a metadata provider version' | '/SHIBUI-1385-2.side' 'SHIBUI-1391: Regex Validation' | '/SHIBUI-1391.side' 'SHIBUI-1407: Metadata source comparison highlights' | '/SHIBUI-1407-1.side' 'SHIBUI-1407: Metadata provider comparison highlights' | '/SHIBUI-1407-2.side' - 'SHIBUI-1503: Non-admin can create metadata source' | '/SHIBUI-1503-1.side' +/* 'SHIBUI-1503: Non-admin can create metadata source' | '/SHIBUI-1503-1.side' 'SHIBUI-1503: User can be deleted' | '/SHIBUI-1503-2.side' 'SHIBUI-1503: User can be enabled' | '/SHIBUI-1503-3.side' 'SHIBUI-1732: Create, use, and delete CEA String' | '/SHIBUI-1732-1.side' @@ -162,6 +166,6 @@ class SeleniumSIDETest extends Specification { 'SHIBUI-2270: Verify full property set' | '/SHIBUI-2270-2.side' 'SHIBUI-2394: Multiple levels of approval' | '/SHIBUI-2394.side' 'SHIBUI-2268: Verify Algorithm Filter' | '/SHIBUI-2268.side' - 'SHIBUI-2269: Verify XML generation of external filters' | '/SHIBUI-2269.side' // Leave this as the last test in order to keep the suite running without strange errors. + 'SHIBUI-2269: Verify XML generation of external filters' | '/SHIBUI-2269.side' // Leave this as the last test in order to keep the suite running without strange errors.*/ } -} +} \ No newline at end of file diff --git a/backend/src/integration/groovy/jp/vmi/selenium/selenese/Runner.java b/backend/src/integration/groovy/jp/vmi/selenium/selenese/Runner.java index acc94ad8d..c1675f5f5 100644 --- a/backend/src/integration/groovy/jp/vmi/selenium/selenese/Runner.java +++ b/backend/src/integration/groovy/jp/vmi/selenium/selenese/Runner.java @@ -1,7 +1,7 @@ package jp.vmi.selenium.selenese; +import com.assertthat.selenium_shutterbug.core.Capture; import com.assertthat.selenium_shutterbug.core.Shutterbug; -import com.assertthat.selenium_shutterbug.utils.web.ScrollStrategy; import jp.vmi.html.result.HtmlResult; import jp.vmi.html.result.HtmlResultHolder; import jp.vmi.junit.result.JUnitResult; @@ -223,7 +223,7 @@ private String takeScreenshot(TakesScreenshot tss, File file, boolean entirePage Map initialCoord = (Map) je.executeScript(getScrollCoord); - Shutterbug.shootPage((WebDriver) tss, ScrollStrategy.BOTH_DIRECTIONS, screenshotScrollTimeout) + Shutterbug.shootPage((WebDriver) tss, Capture.FULL_SCROLL, screenshotScrollTimeout) .withName(FilenameUtils.removeExtension(tmp.getName())) .save(dir.getPath()); @@ -455,7 +455,7 @@ public boolean isInteractive() { @Override public boolean isW3cAction() { - return isW3cAction != null ? isW3cAction : MouseUtils.isW3cAction(getBrowserName()); + return isW3cAction != null ? isW3cAction : MouseUtils.isW3cAction(driver); } /** @@ -846,7 +846,7 @@ public void unhighlight() { * Setup MaxTimeActiveTimer. * @param maxTime the maxTime in milliseconds. */ - void setupMaxTimeTimer(long maxTime) { + public void setupMaxTimeTimer(long maxTime) { this.maxTimeTimer = new MaxTimeActiveTimer(maxTime); } } \ No newline at end of file diff --git a/backend/src/integration/resources/SHIBUI-1333.side b/backend/src/integration/resources/SHIBUI-1333.side index dba5fdfc0..93dae71f3 100644 --- a/backend/src/integration/resources/SHIBUI-1333.side +++ b/backend/src/integration/resources/SHIBUI-1333.side @@ -1306,6 +1306,19 @@ ["xpath=//li[3]/button", "xpath:position"] ], "value": "" + }, { + "id": "065023a8-0f23-42e0-89fc-29ee1c93b7e5", + "comment": "", + "command": "click", + "target": "id=root_relyingPartyOverrides_forceAuthn", + "targets": [ + ["id=root_relyingPartyOverrides_forceAuthn", "id"], + ["css=#root_relyingPartyOverrides_forceAuthn", "css:finder"], + ["xpath=//input[@id='root_relyingPartyOverrides_forceAuthn']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_forceAuthn-group']/div/div/div/input", "xpath:idRelative"], + ["xpath=//div[11]/div/div/div/div/div/input", "xpath:position"] + ], + "value": "" }, { "id": "134bf1f3-1e86-49e7-91de-185e513b02be", "comment": "", @@ -1685,6 +1698,13 @@ ["xpath=//a[contains(.,'somethingElse')]", "xpath:innerText"] ], "value": "" + }, { + "id": "61f50a61-e457-4e65-a957-4f9fd4ab44ce", + "comment": "", + "command": "pause", + "target": "500", + "targets": [], + "value": "" }, { "id": "c8bb3bee-3d61-4324-a3aa-38b78232b969", "comment": "", @@ -1703,7 +1723,49 @@ "id": "667d1702-7451-4200-ae40-3024717c4506", "comment": "", "command": "pause", - "target": "500", + "target": "2000", + "targets": [], + "value": "" + }, { + "id": "25069827-2fd4-42ca-8c4b-7dd6f466b52c", + "comment": "", + "command": "click", + "target": "id=array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", + "targets": [ + ["id=array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", "id"], + ["css=#array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", "css:finder"], + ["xpath=//button[@id='array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_authenticationMethods-group']/div/div/div/div[2]/div/div/div[2]/div/button", "xpath:idRelative"], + ["xpath=//div[2]/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Delete')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "7b71c071-795e-4238-925f-ed03f82dbc51", + "comment": "", + "command": "pause", + "target": "2000", + "targets": [], + "value": "" + }, { + "id": "2d26cc3d-6295-4f68-bdf4-e7e31ea2494f", + "comment": "", + "command": "click", + "target": "id=array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", + "targets": [ + ["id=array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", "id"], + ["css=#array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", "css:finder"], + ["xpath=//button[@id='array-field-addbtn-root_relyingPartyOverrides_authenticationMethods']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[8]/div/div/div/div[3]/div/div/div/div/div/div/button", "xpath:idRelative"], + ["xpath=//div/div/div/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Add ')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "f4e05cb9-9516-4b9b-9038-ffab09d20c0c", + "comment": "", + "command": "pause", + "target": "2000", "targets": [], "value": "" }, { @@ -1711,6 +1773,26 @@ "comment": "", "command": "click", "target": "id=option-selector-root_relyingPartyOverrides_authenticationMethods_0", + "targets": [ + ["id=option-selector-root_relyingPartyOverrides_authenticationMethods_0", "id"], + ["css=#option-selector-root_relyingPartyOverrides_authenticationMethods_0", "css:finder"], + ["xpath=//input[@id='option-selector-root_relyingPartyOverrides_authenticationMethods_0']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_authenticationMethods_0-group']/div/div/div/div/input", "xpath:idRelative"], + ["xpath=//div[2]/div/div/div/div/div/div/div/div/input", "xpath:position"] + ], + "value": "" + }, { + "id": "7857a429-6945-4e31-94b0-f11d78733a43", + "comment": "", + "command": "click", + "target": "css=body", + "targets": [], + "value": "" + }, { + "id": "984faf64-66af-44c1-9490-4c7dc48b9a07", + "comment": "", + "command": "click", + "target": "id=option-selector-root_relyingPartyOverrides_authenticationMethods_0", "targets": [ ["id=option-selector-root_relyingPartyOverrides_authenticationMethods_0", "id"], ["css=#option-selector-root_relyingPartyOverrides_authenticationMethods_0", "css:finder"], @@ -1769,11 +1851,11 @@ "command": "click", "target": "id=option-selector-root_relyingPartyOverrides_authenticationMethods_1", "targets": [ - ["id=option-selector-root_relyingPartyOverrides_authenticationMethods_1", "id"], - ["css=#option-selector-root_relyingPartyOverrides_authenticationMethods_1", "css:finder"], - ["xpath=//input[@id='option-selector-root_relyingPartyOverrides_authenticationMethods_1']", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[8]/div/div/div/div[4]/div/div/div/div/div/div[2]/div[2]/div/div/div/div/div/div/div/input", "xpath:idRelative"], - ["xpath=//div[4]/div/div/div/div/div/div[2]/div[2]/div/div/div/div/div/div/div/input", "xpath:position"] + ["id=option-selector-root_relyingPartyOverrides_authenticationMethods_0", "id"], + ["css=#option-selector-root_relyingPartyOverrides_authenticationMethods_0", "css:finder"], + ["xpath=//input[@id='option-selector-root_relyingPartyOverrides_authenticationMethods_0']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_authenticationMethods_0-group']/div/div/div/div/input", "xpath:idRelative"], + ["xpath=//div[2]/div/div/div/div/div/div/div/div/input", "xpath:position"] ], "value": "" }, { @@ -1915,15 +1997,9 @@ }, { "id": "7c85fe59-dc95-4328-a010-33d1a06a5ce5", "comment": "", - "command": "click", + "command": "assertChecked", "target": "id=root_relyingPartyOverrides_forceAuthn", - "targets": [ - ["id=root_relyingPartyOverrides_forceAuthn", "id"], - ["css=#root_relyingPartyOverrides_forceAuthn", "css:finder"], - ["xpath=//input[@id='root_relyingPartyOverrides_forceAuthn']", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[9]/div/div/div/div[10]/div/div/div/div/div/input", "xpath:idRelative"], - ["xpath=//div[10]/div/div/div/div/div/input", "xpath:position"] - ], + "targets": [], "value": "" }, { "id": "204ea80c-4aac-497f-8956-6370967ba73e", @@ -1935,15 +2011,37 @@ ["xpath=//li[3]/button", "xpath:position"] ], "value": "" + }, { + "id": "ab5d26bb-1372-4088-b7cc-bd061448e0d2", + "comment": "", + "command": "waitForElementVisible", + "target": "css=.fa-check", + "targets": [ + ["css=.fa-check > path", "css:finder"] + ], + "value": "30000" }, { "id": "553dd570-b1bb-43bb-a469-63ee08a09794", "comment": "", "command": "click", - "target": "css=.fa-check", + "target": "css=#attributeRelease\\.checkAll", "targets": [ ["css=.fa-check", "css:finder"] ], "value": "" + }, { + "id": "efb04977-a7d3-4373-969a-828f1be0514b", + "comment": "", + "command": "assertChecked", + "target": "id=root_attributeRelease_8", + "targets": [ + ["id=root_attributeRelease_8", "id"], + ["css=#root_attributeRelease_8", "css:finder"], + ["xpath=//input[@id='root_attributeRelease_8']", "xpath:attributes"], + ["xpath=//div[@id='root_attributeRelease-group']/div/fieldset/table/tbody/tr[9]/td[2]/fieldset/div/div/input", "xpath:idRelative"], + ["xpath=//tr[9]/td[2]/fieldset/div/div/input", "xpath:position"] + ], + "value": "" }, { "id": "4adb7283-b1ad-4553-a934-afc54fa8b04f", "comment": "", @@ -2302,11 +2400,11 @@ "id": "e8b794fc-20e2-4317-8bdd-73eee8e94951", "comment": "", "command": "assertText", - "target": "css=.d-flex:nth-child(3) > .py-2 > span", + "target": "css=div:nth-child(10) .text-truncate", "targets": [ - ["css=.d-flex:nth-child(3) > .py-2 > span", "css:finder"], - ["xpath=//metadata-configuration[@id='configuration']/div/section[8]/div/div[2]/object-property/array-property/div/div/div[3]/div/span", "xpath:idRelative"], - ["xpath=//section[8]/div/div[2]/object-property/array-property/div/div/div[3]/div/span", "xpath:position"] + ["css=div:nth-child(10) .text-truncate", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div/div/section[7]/div/div[2]/div[2]/div/div[10]/div/span[2]", "xpath:idRelative"], + ["xpath=//div[10]/div/span[2]", "xpath:position"] ], "value": "true" }, { diff --git a/backend/src/integration/resources/SHIBUI-1334-1.side b/backend/src/integration/resources/SHIBUI-1334-1.side index 56a095d0b..381c16c8a 100644 --- a/backend/src/integration/resources/SHIBUI-1334-1.side +++ b/backend/src/integration/resources/SHIBUI-1334-1.side @@ -1312,6 +1312,19 @@ ["xpath=//li[3]/button", "xpath:position"] ], "value": "" + }, { + "id": "7c85fe59-dc95-4328-a010-33d1a06a5ce5", + "comment": "", + "command": "click", + "target": "id=root_relyingPartyOverrides_forceAuthn", + "targets": [ + ["id=root_relyingPartyOverrides_forceAuthn", "id"], + ["css=#root_relyingPartyOverrides_forceAuthn", "css:finder"], + ["xpath=//input[@id='root_relyingPartyOverrides_forceAuthn']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[9]/div/div/div/div[10]/div/div/div/div/div/input", "xpath:idRelative"], + ["xpath=//div[10]/div/div/div/div/div/input", "xpath:position"] + ], + "value": "" }, { "id": "134bf1f3-1e86-49e7-91de-185e513b02be", "comment": "", @@ -1703,6 +1716,48 @@ "target": "500", "targets": [], "value": "" + }, { + "id": "bf36e226-da6a-4e64-b1a6-bea0e8f8b340", + "comment": "", + "command": "click", + "target": "id=array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", + "targets": [ + ["id=array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", "id"], + ["css=#array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", "css:finder"], + ["xpath=//button[@id='array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_authenticationMethods-group']/div/div/div/div[2]/div/div/div[2]/div/button", "xpath:idRelative"], + ["xpath=//div[2]/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Delete')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "6852174b-1c29-4fbd-8134-519744fcd8ee", + "comment": "", + "command": "pause", + "target": "500", + "targets": [], + "value": "" + }, { + "id": "59d47cff-9785-48ad-a875-d89f6af4e456", + "comment": "", + "command": "click", + "target": "id=array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", + "targets": [ + ["id=array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", "id"], + ["css=#array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", "css:finder"], + ["xpath=//button[@id='array-field-addbtn-root_relyingPartyOverrides_authenticationMethods']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[8]/div/div/div/div[3]/div/div/div/div/div/div/button", "xpath:idRelative"], + ["xpath=//div/div/div/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Add ')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "a451b482-7f04-4b91-ac5a-d36f5ac8bd40", + "comment": "", + "command": "pause", + "target": "500", + "targets": [], + "value": "" }, { "id": "cbf15c4c-35d9-4f80-ba3d-bfe960048cd1", "comment": "", @@ -1916,19 +1971,6 @@ ["xpath=//a[contains(.,'otherThings')]", "xpath:innerText"] ], "value": "" - }, { - "id": "7c85fe59-dc95-4328-a010-33d1a06a5ce5", - "comment": "", - "command": "click", - "target": "id=root_relyingPartyOverrides_forceAuthn", - "targets": [ - ["id=root_relyingPartyOverrides_forceAuthn", "id"], - ["css=#root_relyingPartyOverrides_forceAuthn", "css:finder"], - ["xpath=//input[@id='root_relyingPartyOverrides_forceAuthn']", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[9]/div/div/div/div[10]/div/div/div/div/div/input", "xpath:idRelative"], - ["xpath=//div[10]/div/div/div/div/div/input", "xpath:position"] - ], - "value": "" }, { "id": "204ea80c-4aac-497f-8956-6370967ba73e", "comment": "", @@ -1943,7 +1985,7 @@ "id": "553dd570-b1bb-43bb-a469-63ee08a09794", "comment": "", "command": "click", - "target": "css=.fa-check", + "target": "css=#attributeRelease\\.checkAll", "targets": [ ["css=.fa-check", "css:finder"], ["xpath=//button[@id='/attributeRelease.checkall']/i", "xpath:idRelative"], @@ -2000,22 +2042,22 @@ "id": "18636780-2feb-458f-97be-cf4a625b22e1", "comment": "", "command": "assertText", - "target": "css=.d-flex:nth-child(5) > .py-2 > span", + "target": "css=div:nth-child(9) .text-truncate", "targets": [ - ["css=.d-flex:nth-child(5) > .py-2 > span", "css:finder"], - ["xpath=//metadata-configuration[@id='configuration']/div/section[9]/div/div[2]/object-property/array-property/div/div[5]/div/span", "xpath:idRelative"], - ["xpath=//div/div[5]/div/span", "xpath:position"] + ["css=div:nth-child(9) .text-truncate", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[3]/div/section[8]/div/div[2]/div[2]/div/div[9]/div/span[2]", "xpath:idRelative"], + ["xpath=//div[9]/div/span[2]", "xpath:position"] ], "value": "true" }, { "id": "a1050ebe-55c5-4eac-8d12-615f3ff1cd72", "comment": "", "command": "assertText", - "target": "css=div:nth-child(9) .text-truncate", + "target": "css=div:nth-child(10) .text-truncate", "targets": [ - ["css=div:nth-child(9) .text-truncate", "css:finder"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[3]/div/section[8]/div/div[2]/div[2]/div/div[9]/div/span[2]", "xpath:idRelative"], - ["xpath=//div[9]/div/span[2]", "xpath:position"] + ["css=div:nth-child(10) .text-truncate", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[3]/div/section[8]/div/div[2]/div[2]/div/div[10]/div/span[2]", "xpath:idRelative"], + ["xpath=//div[10]/div/span[2]", "xpath:position"] ], "value": "true" }, { @@ -2324,11 +2366,11 @@ "id": "e8b794fc-20e2-4317-8bdd-73eee8e94951", "comment": "", "command": "assertText", - "target": "css=.d-flex:nth-child(3) > .py-2 > span", + "target": "css=div:nth-child(9) > .d-flex > .text-truncate", "targets": [ - ["css=.d-flex:nth-child(3) > .py-2 > span", "css:finder"], - ["xpath=//metadata-configuration[@id='configuration']/div/section[8]/div/div[2]/object-property/array-property/div/div/div[3]/div/span", "xpath:idRelative"], - ["xpath=//section[8]/div/div[2]/object-property/array-property/div/div/div[3]/div/span", "xpath:position"] + ["css=div:nth-child(9) > .d-flex > .text-truncate", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div/div/section[7]/div/div[2]/div[2]/div/div[9]/div/span[2]", "xpath:idRelative"], + ["xpath=//div[9]/div/span[2]", "xpath:position"] ], "value": "true" }, { diff --git a/backend/src/integration/resources/SHIBUI-1385-1.side b/backend/src/integration/resources/SHIBUI-1385-1.side index a81d2a2f2..be5b65800 100644 --- a/backend/src/integration/resources/SHIBUI-1385-1.side +++ b/backend/src/integration/resources/SHIBUI-1385-1.side @@ -1334,6 +1334,19 @@ ["xpath=//li[3]/button", "xpath:position"] ], "value": "" + }, { + "id": "7c85fe59-dc95-4328-a010-33d1a06a5ce5", + "comment": "", + "command": "click", + "target": "id=root_relyingPartyOverrides_forceAuthn", + "targets": [ + ["id=root_relyingPartyOverrides_forceAuthn", "id"], + ["css=#root_relyingPartyOverrides_forceAuthn", "css:finder"], + ["xpath=//input[@id='root_relyingPartyOverrides_forceAuthn']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[9]/div/div/div/div[10]/div/div/div/div/div/input", "xpath:idRelative"], + ["xpath=//div[10]/div/div/div/div/div/input", "xpath:position"] + ], + "value": "" }, { "id": "134bf1f3-1e86-49e7-91de-185e513b02be", "comment": "", @@ -1742,6 +1755,47 @@ "target": "500", "targets": [], "value": "" + }, { + "id": "94eaa0f0-8bd4-4bf1-b3ad-944279f73ab2", + "comment": "", + "command": "click", + "target": "id=array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", + "targets": [ + ["id=array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", "id"], + ["css=#array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", "css:finder"], + ["xpath=//button[@id='array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_authenticationMethods-group']/div/div/div/div[2]/div/div/div[2]/div/button", "xpath:idRelative"], + ["xpath=//div[2]/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Delete')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "ee3e9a44-6002-4540-adb4-ab50eefbd899", + "comment": "", + "command": "pause", + "target": "500", + "targets": [], + "value": "" + }, { + "id": "e3e7f5f1-108b-4392-ace7-305685bec4ab", + "comment": "", + "command": "click", + "target": "id=array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", + "targets": [ + ["id=array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", "id"], + ["css=#array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", "css:finder"], + ["xpath=//button[@id='array-field-addbtn-root_relyingPartyOverrides_authenticationMethods']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[8]/div/div/div/div[6]/div/div/div/div/div/div/button", "xpath:idRelative"], + ["xpath=//div[6]/div/div/div/div/div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "d78f24cf-8517-4a85-acbb-e44add1a7d43", + "comment": "", + "command": "pause", + "target": "500", + "targets": [], + "value": "" }, { "id": "cbf15c4c-35d9-4f80-ba3d-bfe960048cd1", "comment": "", @@ -1946,19 +2000,6 @@ ["xpath=//a[contains(.,'otherThings')]", "xpath:innerText"] ], "value": "" - }, { - "id": "7c85fe59-dc95-4328-a010-33d1a06a5ce5", - "comment": "", - "command": "click", - "target": "id=root_relyingPartyOverrides_forceAuthn", - "targets": [ - ["id=root_relyingPartyOverrides_forceAuthn", "id"], - ["css=#root_relyingPartyOverrides_forceAuthn", "css:finder"], - ["xpath=//input[@id='root_relyingPartyOverrides_forceAuthn']", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[9]/div/div/div/div[10]/div/div/div/div/div/input", "xpath:idRelative"], - ["xpath=//div[10]/div/div/div/div/div/input", "xpath:position"] - ], - "value": "" }, { "id": "204ea80c-4aac-497f-8956-6370967ba73e", "comment": "", @@ -1973,11 +2014,24 @@ "id": "553dd570-b1bb-43bb-a469-63ee08a09794", "comment": "", "command": "click", - "target": "css=.fa-check", + "target": "css=#attributeRelease\\.checkAll", "targets": [ ["css=.fa-check", "css:finder"] ], "value": "" + }, { + "id": "f2e019e5-db1e-493a-90d3-182d2c2f7690", + "comment": "", + "command": "assertChecked", + "target": "id=root_attributeRelease_0", + "targets": [ + ["id=root_attributeRelease_0", "id"], + ["css=#root_attributeRelease_0", "css:finder"], + ["xpath=//input[@id='root_attributeRelease_0']", "xpath:attributes"], + ["xpath=//div[@id='root_attributeRelease-group']/div/fieldset/table/tbody/tr/td[2]/fieldset/div/div/input", "xpath:idRelative"], + ["xpath=//input", "xpath:position"] + ], + "value": "" }, { "id": "4adb7283-b1ad-4553-a934-afc54fa8b04f", "comment": "", diff --git a/backend/src/integration/resources/SHIBUI-1392.side b/backend/src/integration/resources/SHIBUI-1392.side index 1dc727268..94cbd2119 100644 --- a/backend/src/integration/resources/SHIBUI-1392.side +++ b/backend/src/integration/resources/SHIBUI-1392.side @@ -395,11 +395,11 @@ "id": "5699a5a2-d3df-4f99-b95d-f340f9156ffe", "comment": "", "command": "click", - "target": "css=.dropdown-item:nth-child(3)", + "target": "css=.show > .dropdown-item:nth-child(3)", "targets": [ - ["css=.dropdown-item:nth-child(3)", "css:finder"], + ["css=.show > .dropdown-item:nth-child(3)", "css:finder"], ["xpath=(//button[@type='button'])[16]", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div/div[2]/div[2]/div[2]/div/form/div/div/div/div[3]/div/div/div/fieldset/div/div/div/div/div/button[3]", "xpath:idRelative"], + ["xpath=//div[@id='root_nameIdFormatFilterTarget-group']/div/fieldset/div/div/div/div/div/button[3]", "xpath:idRelative"], ["xpath=//button[3]", "xpath:position"], ["xpath=//button[contains(.,'Script')]", "xpath:innerText"] ], @@ -508,20 +508,20 @@ ], "value": "eval(true);" }, { - "id": "4ec2c493-85e4-403b-9b09-031c5728f498", - "comment": "", - "command": "open", - "target": "/api/heheheheheheheWipeout", - "targets": [], - "value": "" - }, { - "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", - "comment": "", - "command": "assertText", - "target": "css=body", - "targets": [], - "value": "yes, you did it" - }] + "id": "4ec2c493-85e4-403b-9b09-031c5728f498", + "comment": "", + "command": "open", + "target": "/api/heheheheheheheWipeout", + "targets": [], + "value": "" + }, { + "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", + "comment": "", + "command": "assertText", + "target": "css=body", + "targets": [], + "value": "yes, you did it" + }] }], "suites": [{ "id": "4f69a686-bcaa-4963-84af-2e592e8c8842", diff --git a/backend/src/integration/resources/SHIBUI-1407-1.side b/backend/src/integration/resources/SHIBUI-1407-1.side index b86ffc627..d1f832002 100644 --- a/backend/src/integration/resources/SHIBUI-1407-1.side +++ b/backend/src/integration/resources/SHIBUI-1407-1.side @@ -1327,6 +1327,19 @@ ["xpath=//li[3]/button", "xpath:position"] ], "value": "" + }, { + "id": "7c85fe59-dc95-4328-a010-33d1a06a5ce5", + "comment": "", + "command": "click", + "target": "id=root_relyingPartyOverrides_forceAuthn", + "targets": [ + ["id=root_relyingPartyOverrides_forceAuthn", "id"], + ["css=#root_relyingPartyOverrides_forceAuthn", "css:finder"], + ["xpath=//input[@id='root_relyingPartyOverrides_forceAuthn']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[9]/div/div/div/div[10]/div/div/div/div/div/input", "xpath:idRelative"], + ["xpath=//div[10]/div/div/div/div/div/input", "xpath:position"] + ], + "value": "" }, { "id": "134bf1f3-1e86-49e7-91de-185e513b02be", "comment": "", @@ -1742,6 +1755,47 @@ "target": "500", "targets": [], "value": "" + }, { + "id": "47ce7b12-97c8-4393-9f28-2fbc0537627f", + "comment": "", + "command": "click", + "target": "id=array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", + "targets": [ + ["id=array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", "id"], + ["css=#array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0", "css:finder"], + ["xpath=//button[@id='array-field-deletebtn-root_relyingPartyOverrides_authenticationMethods-0']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_authenticationMethods-group']/div/div/div/div[2]/div/div/div[2]/div/button", "xpath:idRelative"], + ["xpath=//div[2]/div/button", "xpath:position"], + ["xpath=//button[contains(.,'Delete')]", "xpath:innerText"] + ], + "value": "" + }, { + "id": "3c94a090-d35c-4412-80cb-bbe7bf6ea6d9", + "comment": "", + "command": "pause", + "target": "500", + "targets": [], + "value": "" + }, { + "id": "3d6a17e4-d895-45aa-9dd5-22cc130ebbfe", + "comment": "", + "command": "click", + "target": "id=array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", + "targets": [ + ["id=array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", "id"], + ["css=#array-field-addbtn-root_relyingPartyOverrides_authenticationMethods", "css:finder"], + ["xpath=//button[@id='array-field-addbtn-root_relyingPartyOverrides_authenticationMethods']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[8]/div/div/div/div[6]/div/div/div/div/div/div/button", "xpath:idRelative"], + ["xpath=//div[6]/div/div/div/div/div/div/button", "xpath:position"] + ], + "value": "" + }, { + "id": "cb08a311-29b6-41a6-bfd1-38c12e9aca93", + "comment": "", + "command": "pause", + "target": "500", + "targets": [], + "value": "" }, { "id": "cbf15c4c-35d9-4f80-ba3d-bfe960048cd1", "comment": "", @@ -1945,19 +1999,6 @@ ["xpath=//a[contains(.,'otherThings')]", "xpath:innerText"] ], "value": "" - }, { - "id": "7c85fe59-dc95-4328-a010-33d1a06a5ce5", - "comment": "", - "command": "click", - "target": "id=root_relyingPartyOverrides_forceAuthn", - "targets": [ - ["id=root_relyingPartyOverrides_forceAuthn", "id"], - ["css=#root_relyingPartyOverrides_forceAuthn", "css:finder"], - ["xpath=//input[@id='root_relyingPartyOverrides_forceAuthn']", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[9]/div/div/div/div[10]/div/div/div/div/div/input", "xpath:idRelative"], - ["xpath=//div[10]/div/div/div/div/div/input", "xpath:position"] - ], - "value": "" }, { "id": "204ea80c-4aac-497f-8956-6370967ba73e", "comment": "", @@ -2325,11 +2366,11 @@ "id": "e8b794fc-20e2-4317-8bdd-73eee8e94951", "comment": "", "command": "assertText", - "target": "css=div:nth-child(9) .text-truncate", + "target": "css=div:nth-child(10) .text-truncate", "targets": [ - ["css=div:nth-child(9) .text-truncate", "css:finder"], - ["xpath=//div[@id='root']/div/main/div/section/div/div/section[7]/div/div[2]/div[2]/div/div[9]/div/span[2]", "xpath:idRelative"], - ["xpath=//div[9]/div/span[2]", "xpath:position"] + ["css=div:nth-child(10) .text-truncate", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div/div/section[7]/div/div[2]/div[2]/div/div[10]/div/span[2]", "xpath:idRelative"], + ["xpath=//div[10]/div/span[2]", "xpath:position"] ], "value": "true" }, { diff --git a/backend/src/integration/resources/SHIBUI-1674-1.side b/backend/src/integration/resources/SHIBUI-1674-1.side index 98b22bea0..d4e5db747 100644 --- a/backend/src/integration/resources/SHIBUI-1674-1.side +++ b/backend/src/integration/resources/SHIBUI-1674-1.side @@ -119,16 +119,26 @@ "id": "93d20204-7dfa-44c0-8e61-6741e73594f5", "comment": "", "command": "waitForElementPresent", - "target": "css=.mb-3:nth-child(3) .info-icon path", - "targets": [], + "target": "id=info-tooltip.entity-id", + "targets": [ + ["id=info-tooltip.entity-id", "id"], + ["css=#info-tooltip\\.entity-id", "css:finder"], + ["xpath=//button[@id='info-tooltip.entity-id']", "xpath:attributes"], + ["xpath=//div[@id='root_entityId-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/label/button", "xpath:position"] + ], "value": "30000" }, { "id": "a7b2b925-2274-4dcc-a4e3-3b727c9a047a", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(3) .info-icon path", + "target": "css=#info-tooltip\\.entity-id > svg > path", "targets": [ - ["css=.mb-3:nth-child(3) .info-icon path", "css:finder"] + ["id=info-tooltip.entity-id", "id"], + ["css=#info-tooltip\\.entity-id", "css:finder"], + ["xpath=//button[@id='info-tooltip.entity-id']", "xpath:attributes"], + ["xpath=//div[@id='root_entityId-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -176,13 +186,25 @@ ["xpath=//li[2]/button", "xpath:position"] ], "value": "" + }, { + "id": "551a89f9-021a-42b9-b890-5fafb024a63e", + "comment": "", + "command": "waitForElementPresent", + "target": "id=info-tooltip.organization-name", + "targets": [], + "value": "30000" }, { "id": "ca21608c-baaa-400d-b8bd-8ad0eb4b7a53", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(1) > .col-12 > .mb-3 path", + "target": "css=#info-tooltip\\.organization-name > svg > path", "targets": [ - ["css=.row:nth-child(1) > .col-12 > .mb-3 path", "css:finder"] + ["id=info-tooltip.organization-name", "id"], + ["css=#info-tooltip\\.organization-name", "css:finder"], + ["xpath=//button[@id='info-tooltip.organization-name']", "xpath:attributes"], + ["xpath=//div[@id='root_organization_name-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -208,9 +230,13 @@ "id": "b56c1325-5438-4b5a-b626-79b030d04313", "comment": "", "command": "mouseOver", - "target": "css=.d-empty-none:nth-child(2) > .mb-3:nth-child(1) path", + "target": "id=info-tooltip.mdui-privacy-statement-url", "targets": [ - ["css=.d-empty-none:nth-child(2) > .mb-3:nth-child(1) path", "css:finder"] + ["id=info-tooltip.mdui-privacy-statement-url", "id"], + ["css=#info-tooltip\\.mdui-privacy-statement-url", "css:finder"], + ["xpath=//button[@id='info-tooltip.mdui-privacy-statement-url']", "xpath:attributes"], + ["xpath=//div[@id='root_mdui_privacyStatementUrl-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -236,9 +262,14 @@ "id": "f041b735-85f2-4005-ad80-16a82c683f60", "comment": "", "command": "mouseOver", - "target": "css=.ms-2 > path", + "target": "id=info-tooltip.protocol-support-enumeration", "targets": [ - ["css=.ms-2 > path", "css:finder"] + ["id=info-tooltip.protocol-support-enumeration", "id"], + ["css=#info-tooltip\\.protocol-support-enumeration", "css:finder"], + ["xpath=//button[@id='info-tooltip.protocol-support-enumeration']", "xpath:attributes"], + ["xpath=//div[@id='root_serviceProviderSsoDescriptor_protocolSupportEnum-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -264,9 +295,14 @@ "id": "be222605-95ec-4a48-b02c-9b29f47e1ef5", "comment": "", "command": "mouseOver", - "target": "css=.fa-circle-info > path", + "target": "id=info-tooltip.logout-endpoints", "targets": [ - ["css=.fa-circle-info > path", "css:finder"] + ["id=info-tooltip.logout-endpoints", "id"], + ["css=#info-tooltip\\.logout-endpoints", "css:finder"], + ["xpath=//button[@id='info-tooltip.logout-endpoints']", "xpath:attributes"], + ["xpath=//div[@id='root_logoutEndpoints-group']/div/div/div/div/button[2]", "xpath:idRelative"], + ["xpath=//button[2]", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -292,9 +328,14 @@ "id": "397a8204-b6b1-442f-a491-4c89b1d320ff", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(1) > div > .mb-3 path", + "target": "id=info-tooltip.authentication-requests-signed", "targets": [ - ["css=.mb-3:nth-child(1) > div > .mb-3 path", "css:finder"] + ["id=info-tooltip.authentication-requests-signed", "id"], + ["css=#info-tooltip\\.authentication-requests-signed", "css:finder"], + ["xpath=//button[@id='info-tooltip.authentication-requests-signed']", "xpath:attributes"], + ["xpath=//div[@id='root_securityInfo_authenticationRequestsSigned-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -320,9 +361,14 @@ "id": "6cfdcca2-1deb-4055-9b75-afbfbf728783", "comment": "", "command": "mouseOver", - "target": "css=.fa-circle-info > path", + "target": "id=info-tooltip.assertion-consumer-service-endpoints", "targets": [ - ["css=.fa-circle-info > path", "css:finder"] + ["id=info-tooltip.assertion-consumer-service-endpoints", "id"], + ["css=#info-tooltip\\.assertion-consumer-service-endpoints", "css:finder"], + ["xpath=//button[@id='info-tooltip.assertion-consumer-service-endpoints']", "xpath:attributes"], + ["xpath=//div[@id='root_assertionConsumerServices-group']/div/div/div/div/button[2]", "xpath:idRelative"], + ["xpath=//button[2]", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -348,9 +394,13 @@ "id": "56f49fe8-340c-46fa-bda9-0b3c0de98bdb", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(2) path:nth-child(1)", + "target": "id=info-tooltip.sign-assertion", "targets": [ - ["css=.row:nth-child(2) path:nth-child(1)", "css:finder"] + ["id=info-tooltip.sign-assertion", "id"], + ["css=#info-tooltip\\.sign-assertion", "css:finder"], + ["xpath=//button[@id='info-tooltip.sign-assertion']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_signAssertion-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/div/div/div/label/span/button", "xpath:position"] ], "value": "" }, { @@ -360,13 +410,6 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "Sign Assertion declares that the service provider wants the element to be digitally signed." - }, { - "id": "5c718136-9eba-46f9-b93b-da820abe2719", - "comment": "", - "command": "mouseOut", - "target": "css=.row:nth-child(1) > .col-12:nth-child(1) > .mb-3:nth-child(1) path:nth-child(1)", - "targets": [], - "value": "" }, { "id": "268b3df4-a184-4a39-bec3-70dfc598b073", "comment": "", @@ -385,9 +428,13 @@ "id": "54a74399-69aa-4412-85c2-a5bbb543d8be", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(11) path:nth-child(1)", + "target": "id=info-tooltip.dont-sign-response", "targets": [ - ["css=.row:nth-child(9) path:nth-child(1)", "css:finder"] + ["id=info-tooltip.dont-sign-response", "id"], + ["css=#info-tooltip\\.dont-sign-response", "css:finder"], + ["xpath=//button[@id='info-tooltip.dont-sign-response']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_dontSignResponse-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//div[11]/div/div/div/div/div/label/span/button", "xpath:position"] ], "value": "" }, { @@ -397,13 +444,6 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "Do not sign the full authentication response to the service provider. Enabling this property will reduce the size of the response to service providers who may have limitations to the size of the response." - }, { - "id": "87036a6f-aebd-4ef1-8cb8-03d082676c03", - "comment": "", - "command": "mouseOut", - "target": "css=.row:nth-child(8) path:nth-child(1)", - "targets": [], - "value": "" }, { "id": "31fdddde-2472-4921-950c-ca56555c2d5b", "comment": "", @@ -493,9 +533,13 @@ "id": "c974e795-454e-4271-86ef-609e3538b28f", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(1) > .col-12 .svg-inline--fa", + "target": "id=info-tooltip.organization-url", "targets": [ - ["css=.row:nth-child(1) > .col-12 .svg-inline--fa", "css:finder"] + ["id=info-tooltip.organization-url", "id"], + ["css=#info-tooltip\\.organization-url", "css:finder"], + ["xpath=//button[@id='info-tooltip.organization-url']", "xpath:attributes"], + ["xpath=//div[@id='root_organization_url-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[3]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -504,7 +548,7 @@ "command": "assertText", "target": "css=div[role=\"tooltip\"]", "targets": [], - "value": "Name of the organization standing up the entity." + "value": "URL of the organization standing up the entity." }, { "id": "2177712e-3ffa-4d70-a546-4425fa6b6565", "comment": "", @@ -522,9 +566,13 @@ "id": "5b8ce0c5-f2d1-4bca-acb1-c22a3ed02fd7", "comment": "", "command": "mouseOver", - "target": "css=.d-empty-none:nth-child(1) > .mb-3:nth-child(2) path", + "target": "id=info-tooltip.mdui-information-url", "targets": [ - ["css=.d-empty-none:nth-child(1) > .mb-3:nth-child(2) path", "css:finder"] + ["id=info-tooltip.mdui-information-url", "id"], + ["css=#info-tooltip\\.mdui-information-url", "css:finder"], + ["xpath=//button[@id='info-tooltip.mdui-information-url']", "xpath:attributes"], + ["xpath=//div[@id='root_mdui_informationUrl-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -551,9 +599,14 @@ "id": "013648f1-be7a-4fbf-a5c4-07482ca9fc96", "comment": "", "command": "mouseOver", - "target": "css=.ms-2 > path", + "target": "id=info-tooltip.protocol-support-enumeration", "targets": [ - ["css=.ms-2 > path", "css:finder"] + ["id=info-tooltip.protocol-support-enumeration", "id"], + ["css=#info-tooltip\\.protocol-support-enumeration", "css:finder"], + ["xpath=//button[@id='info-tooltip.protocol-support-enumeration']", "xpath:attributes"], + ["xpath=//div[@id='root_serviceProviderSsoDescriptor_protocolSupportEnum-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -593,8 +646,14 @@ "id": "825b4bb3-e40e-4b02-a053-5f8e15b9b672", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(1) > .col-12 .btn > .svg-inline--fa", - "targets": [], + "target": "id=info-tooltip.url", + "targets": [ + ["id=info-tooltip.url", "id"], + ["css=#info-tooltip\\.url", "css:finder"], + ["xpath=//button[@id='info-tooltip.url']", "xpath:attributes"], + ["xpath=//div[@id='root_logoutEndpoints_0_url-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"] + ], "value": "" }, { "id": "8b309a28-ff0d-4e6b-8c49-26a9e13822d8", @@ -635,9 +694,13 @@ "id": "84415564-f9d8-4b75-a6cf-5c269cb8eb9f", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(2) path", + "target": "id=info-tooltip.want-assertions-signed", "targets": [ - ["css=.mb-3:nth-child(2) path", "css:finder"] + ["id=info-tooltip.want-assertions-signed", "id"], + ["css=#info-tooltip\\.want-assertions-signed", "css:finder"], + ["xpath=//button[@id='info-tooltip.want-assertions-signed']", "xpath:attributes"], + ["xpath=//div[@id='root_securityInfo_wantAssertionsSigned-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -679,9 +742,13 @@ "id": "ff8f4f9f-0832-4a08-910d-778156869d6a", "comment": "", "command": "mouseOver", - "target": "css=.mb-3 > .form-label > .btn path", + "target": "id=info-tooltip.assertion-consumer-service-location-binding", "targets": [ - ["css=.mb-3 > .form-label > .btn path", "css:finder"] + ["id=info-tooltip.assertion-consumer-service-location-binding", "id"], + ["css=#info-tooltip\\.assertion-consumer-service-location-binding", "css:finder"], + ["xpath=//button[@id='info-tooltip.assertion-consumer-service-location-binding']", "xpath:attributes"], + ["xpath=//div[@id='root_assertionConsumerServices_0_binding-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -719,9 +786,13 @@ "id": "2928ba27-b934-499e-8dda-8441dbbb463d", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(10) path:nth-child(1)", + "target": "id=info-tooltip.force-authn", "targets": [ - ["css=.row:nth-child(8) path:nth-child(1)", "css:finder"] + ["id=info-tooltip.force-authn", "id"], + ["css=#info-tooltip\\.force-authn", "css:finder"], + ["xpath=//button[@id='info-tooltip.force-authn']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_forceAuthn-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//div[10]/div/div/div/div/div/label/span/button", "xpath:position"] ], "value": "" }, { @@ -730,7 +801,11 @@ "command": "assertText", "target": "css=div[role=\"tooltip\"]", "targets": [ - ["css=.row:nth-child(1) > .col-12 .btn > .svg-inline--fa", "css:finder"] + ["id=info-tooltip.force-authn", "id"], + ["css=#info-tooltip\\.force-authn", "css:finder"], + ["xpath=//button[@id='info-tooltip.force-authn']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_forceAuthn-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//div[10]/div/div/div/div/div/label/span/button", "xpath:position"] ], "value": "Disallows use (or reuse) of authentication results and login flows that don't provide a real-time proof of user presence in the login process" }, { diff --git a/backend/src/integration/resources/SHIBUI-1674-2.side b/backend/src/integration/resources/SHIBUI-1674-2.side index b220ee571..390378425 100644 --- a/backend/src/integration/resources/SHIBUI-1674-2.side +++ b/backend/src/integration/resources/SHIBUI-1674-2.side @@ -146,9 +146,13 @@ "id": "2f6171c0-f11f-4d3a-99fe-4ba3ef743f3f", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(2) .info-icon > .svg-inline--fa", + "target": "id=info-tooltip.metadata-provider-type", "targets": [ - ["css=.mb-3:nth-child(2) .info-icon > .svg-inline--fa", "css:finder"] + ["id=info-tooltip.metadata-provider-type", "id"], + ["css=#info-tooltip\\.metadata-provider-type", "css:finder"], + ["xpath=//button[@id='info-tooltip.metadata-provider-type']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div/div/div/form/div[2]/label/button", "xpath:idRelative"], + ["xpath=//div[2]/label/button", "xpath:position"] ], "value": "" }, { @@ -240,9 +244,13 @@ "id": "c3323c50-da68-42be-8ecf-1754be5f402e", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(7) path", + "target": "id=info-tooltip.fail-fast-init", "targets": [ - ["css=.mb-3:nth-child(7) path", "css:finder"] + ["id=info-tooltip.fail-fast-init", "id"], + ["css=#info-tooltip\\.fail-fast-init", "css:finder"], + ["xpath=//button[@id='info-tooltip.fail-fast-init']", "xpath:attributes"], + ["xpath=//div[@id='root_failFastInitialization-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[7]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -250,7 +258,13 @@ "comment": "", "command": "assertText", "target": "css=div[role=\"tooltip\"]", - "targets": [], + "targets": [ + ["id=info-tooltip.fail-fast-init", "id"], + ["css=#info-tooltip\\.fail-fast-init", "css:finder"], + ["xpath=//button[@id='info-tooltip.fail-fast-init']", "xpath:attributes"], + ["xpath=//div[@id='root_failFastInitialization-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[7]/div/div/label/button", "xpath:position"] + ], "value": "Whether to fail initialization of the underlying MetadataResolverService (and possibly the IdP as a whole) if the initialization of a metadata provider fails. When false, the IdP may start, and will continue to attempt to reload valid metadata if configured to do so, but operations that require valid metadata will fail until it does." }, { "id": "3a2ee060-bea9-4ee6-86ac-0bfec851a0f4", @@ -268,9 +282,13 @@ "id": "f9c24b89-5e95-439f-9f94-13e5482ba269", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(2) .form-label > .btn > .svg-inline--fa", + "target": "id=info-tooltip.max-refresh-delay", "targets": [ - ["css=.row:nth-child(2) .form-label > .btn > .svg-inline--fa", "css:finder"] + ["id=info-tooltip.max-refresh-delay", "id"], + ["css=#info-tooltip\\.max-refresh-delay", "css:finder"], + ["xpath=//button[@id='info-tooltip.max-refresh-delay']", "xpath:attributes"], + ["xpath=//div[@id='root_reloadableMetadataResolverAttributes_maxRefreshDelay-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -296,9 +314,13 @@ "id": "8b9ab8e9-4856-4382-824b-37332e504342", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(3) > .col-12 > .mb-3 .form-label path", + "target": "id=info-tooltip.certificate-file", "targets": [ - ["css=.row:nth-child(3) > .col-12 > .mb-3 .form-label path", "css:finder"] + ["id=info-tooltip.certificate-file", "id"], + ["css=#info-tooltip\\.certificate-file", "css:finder"], + ["xpath=//button[@id='info-tooltip.certificate-file']", "xpath:attributes"], + ["xpath=//div[@id='root_metadataFilters_1_certificateFile-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[3]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -409,9 +431,14 @@ "id": "3e9efc71-b8e5-4d0e-99f1-5605f0984768", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(1) .info-icon path", + "target": "id=info-tooltip.metadata-provider-name", "targets": [ - ["css=.mb-3:nth-child(1) .info-icon path", "css:finder"] + ["id=info-tooltip.metadata-provider-name", "id"], + ["css=#info-tooltip\\.metadata-provider-name", "css:finder"], + ["xpath=//button[@id='info-tooltip.metadata-provider-name']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div/div/div/form/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -470,9 +497,13 @@ "id": "1086624e-fc87-470b-85bf-246fd3a42f64", "comment": "", "command": "mouseOver", - "target": "css=.d-block path", + "target": "id=info-tooltip.do-resolver-initialization", "targets": [ - ["css=.d-block path", "css:finder"] + ["id=info-tooltip.do-resolver-initialization", "id"], + ["css=#info-tooltip\\.do-resolver-initialization", "css:finder"], + ["xpath=//button[@id='info-tooltip.do-resolver-initialization']", "xpath:attributes"], + ["xpath=//div[@id='root_doInitialization-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[3]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -498,9 +529,13 @@ "id": "d910ef5c-1e0f-48b8-bfbb-3dc0f2e65864", "comment": "", "command": "mouseOver", - "target": "css=.ms-2:nth-child(2) > path", + "target": "id=info-tooltip.refresh-delay-factor", "targets": [ - ["css=.ms-2:nth-child(2) > path", "css:finder"] + ["id=info-tooltip.refresh-delay-factor", "id"], + ["css=#info-tooltip\\.refresh-delay-factor", "css:finder"], + ["xpath=//button[@id='info-tooltip.refresh-delay-factor']", "xpath:attributes"], + ["xpath=//div[@id='root_reloadableMetadataResolverAttributes_refreshDelayFactor-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[3]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -649,9 +684,14 @@ "id": "cc967bcc-786d-4fa3-a94e-d4571597a3dd", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(2) .btn path", + "target": "id=info-tooltip.xml-id", "targets": [ - ["css=.mb-3:nth-child(2) .btn path", "css:finder"] + ["id=info-tooltip.xml-id", "id"], + ["css=#info-tooltip\\.xml-id", "css:finder"], + ["xpath=//button[@id='info-tooltip.xml-id']", "xpath:attributes"], + ["xpath=//div[@id='root_xmlId-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -660,7 +700,7 @@ "command": "assertText", "target": "css=div[role=\"tooltip\"]", "targets": [], - "value": "Convenience mechanism for wiring a FilesystemLoadSaveManager, loading from the specified source directory in the local filesystem. This attribute will be ignored if sourceManagerRef is also specified. Either this attribute or sourceManagerRef is required." + "value": "Identifier for logging, identification for command line reload, etc." }, { "id": "f5837e71-6c43-449e-8580-ee1ace17da5e", "comment": "", @@ -677,9 +717,13 @@ "id": "718a6f12-5933-4a38-a84f-c9061d238656", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(2) path", + "target": "id=info-tooltip.min-cache-duration", "targets": [ - ["css=.row:nth-child(2) path", "css:finder"] + ["id=info-tooltip.min-cache-duration", "id"], + ["css=#info-tooltip\\.min-cache-duration", "css:finder"], + ["xpath=//button[@id='info-tooltip.min-cache-duration']", "xpath:attributes"], + ["xpath=//div[@id='root_dynamicMetadataResolverAttributes_minCacheDuration-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -689,6 +733,13 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "The minimum duration for which metadata will be cached before it is refreshed." + }, { + "id": "6afe9417-450d-4820-8ed4-300188f38196", + "comment": "", + "command": "mouseOut", + "target": "css=.row:nth-child(2) path", + "targets": [], + "value": "" }, { "id": "da0ce3d8-75e6-4b84-b3a7-8b423399044e", "comment": "", @@ -707,9 +758,13 @@ "id": "5f3a17b8-9ab0-43b9-a6e9-47dfc036760a", "comment": "", "command": "mouseOver", - "target": "css=.d-block path", + "target": "id=info-tooltip.remove-idle-entity-data", "targets": [ - ["css=.d-block path", "css:finder"] + ["id=info-tooltip.remove-idle-entity-data", "id"], + ["css=#info-tooltip\\.remove-idle-entity-data", "css:finder"], + ["xpath=//button[@id='info-tooltip.remove-idle-entity-data']", "xpath:attributes"], + ["xpath=//div[@id='root_dynamicMetadataResolverAttributes_removeIdleEntityData-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[5]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -884,9 +939,13 @@ "id": "b0bbc709-2c6a-4600-8eac-d3b4182b3bb8", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(3) > .col-12 > .mb-3 .btn > .svg-inline--fa", + "target": "id=info-tooltip.match", "targets": [ - ["css=.row:nth-child(3) > .col-12 > .mb-3 .btn > .svg-inline--fa", "css:finder"] + ["id=info-tooltip.match", "id"], + ["css=#info-tooltip\\.match", "css:finder"], + ["xpath=//button[@id='info-tooltip.match']", "xpath:attributes"], + ["xpath=//div[@id='root_metadataRequestURLConstructionScheme_match-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[3]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -912,9 +971,13 @@ "id": "bdfb17e5-a4c9-408b-858b-7e2fddd7c350", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(2) path", + "target": "id=info-tooltip.min-cache-duration", "targets": [ - ["css=.row:nth-child(2) path", "css:finder"] + ["id=info-tooltip.min-cache-duration", "id"], + ["css=#info-tooltip\\.min-cache-duration", "css:finder"], + ["xpath=//button[@id='info-tooltip.min-cache-duration']", "xpath:attributes"], + ["xpath=//div[@id='root_dynamicMetadataResolverAttributes_minCacheDuration-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -924,6 +987,47 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "The minimum duration for which metadata will be cached before it is refreshed." + }, { + "id": "687d2359-86f5-45de-bbfd-6557f62fd6d1", + "comment": "", + "command": "mouseOut", + "target": "css=.row:nth-child(2) path", + "targets": [], + "value": "" + }, { + "id": "fba778ef-2d9a-4fe9-9e6a-291560e3d807", + "comment": "", + "command": "click", + "target": "css=body", + "targets": [], + "value": "" + }, { + "id": "86512870-b695-44b6-a112-ea60375586f4", + "comment": "", + "command": "pause", + "target": "1000", + "targets": [], + "value": "" + }, { + "id": "8f77dc16-2b54-46d7-b6bd-bf6fd046e8b6", + "comment": "", + "command": "mouseOver", + "target": "id=info-tooltip.initialize-from-persistent-cache-in-background", + "targets": [ + ["id=info-tooltip.initialize-from-persistent-cache-in-background", "id"], + ["css=#info-tooltip\\.initialize-from-persistent-cache-in-background", "css:finder"], + ["xpath=//button[@id='info-tooltip.initialize-from-persistent-cache-in-background']", "xpath:attributes"], + ["xpath=//div[@id='root_dynamicMetadataResolverAttributes_initializeFromPersistentCacheInBackground-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[8]/div/div/div/div/label/button", "xpath:position"] + ], + "value": "" + }, { + "id": "3fd29e97-1178-4bb5-9e76-22e89bca717c", + "comment": "", + "command": "assertText", + "target": "css=div[role=\"tooltip\"]", + "targets": [], + "value": "Flag indicating whether the system should initialize from the persistent cache in the background. Initializing from the cache in the background will improve IdP startup times." }, { "id": "d1ee0afc-651b-4da5-bd99-eac47bbceb78", "comment": "", @@ -936,6 +1040,37 @@ ["xpath=//li[2]/button", "xpath:position"] ], "value": "" + }, { + "id": "03bb682c-544f-44f4-9b05-a4d80ac6d022", + "comment": "", + "command": "waitForElementVisible", + "target": "css=#root_metadataFilters_2_removeEmptyEntitiesDescriptors-group path", + "targets": [ + ["css=#root_metadataFilters_2_removeEmptyEntitiesDescriptors-group path", "css:finder"] + ], + "value": "30000" + }, { + "id": "aea0e033-111e-4a5d-8038-ec222786a695", + "comment": "", + "command": "mouseOver", + "target": "id=info-tooltip.remove-empty-entities-descriptors", + "targets": [ + ["id=info-tooltip.remove-empty-entities-descriptors", "id"], + ["css=#info-tooltip\\.remove-empty-entities-descriptors", "css:finder"], + ["xpath=//button[@id='info-tooltip.remove-empty-entities-descriptors']", "xpath:attributes"], + ["xpath=//div[@id='root_metadataFilters_2_removeEmptyEntitiesDescriptors-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//div[4]/div/div/div/div/div/label/span/button", "xpath:position"] + ], + "value": "" + }, { + "id": "80e0d456-3951-4858-8423-7e04d6debb96", + "comment": "", + "command": "assertText", + "target": "css=div[role=\"tooltip\"]", + "targets": [ + ["css=#root_metadataFilters_2_removeRolelessEntityDescriptors-group path", "css:finder"] + ], + "value": "Controls whether to keep entities descriptors that contain no entity descriptors. Note: If this attribute is set to false, the resulting output may not be schema-valid since an element must include at least one child element, either an element or an element." }, { "id": "148a84ef-0353-425d-9a63-79ccaa01478d", "comment": "", @@ -1075,9 +1210,13 @@ "id": "52604851-991e-4055-b926-21a8eae2d293", "comment": "", "command": "mouseOver", - "target": "css=.mb-3 > div > .form-label > .btn path", + "target": "id=info-tooltip.external-description", "targets": [ - ["css=.mb-3 > div > .form-label > .btn path", "css:finder"] + ["id=info-tooltip.external-description", "id"], + ["css=#info-tooltip\\.external-description", "css:finder"], + ["xpath=//button[@id='info-tooltip.external-description']", "xpath:attributes"], + ["xpath=//div[@id='root_description-group']/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/label/button", "xpath:position"] ], "value": "" }, { @@ -1172,9 +1311,14 @@ "id": "7fa035f2-c14b-4452-8437-f15901428d76", "comment": "", "command": "mouseOver", - "target": "css=.fa-circle-info > path", + "target": "id=info-tooltip.metadata-filter-type", "targets": [ - ["css=.fa-circle-info > path", "css:finder"] + ["id=info-tooltip.metadata-filter-type", "id"], + ["css=#info-tooltip\\.metadata-filter-type", "css:finder"], + ["xpath=//button[@id='info-tooltip.metadata-filter-type']", "xpath:attributes"], + ["xpath=//div[@id='root']/div/main/div/section/div[2]/div/div/div/div/div/form/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -1204,6 +1348,39 @@ ["xpath=//select", "xpath:position"] ], "value": "label=EntityAttributes" + }, { + "id": "405609c5-3894-4204-a6e6-182e619d2c04", + "comment": "", + "command": "waitForElementVisible", + "target": "id=info-Indicates the type of search to be performed.", + "targets": [ + ["id=info-Indicates the type of search to be performed.", "id"], + ["css=#info-Indicates\\ the\\ type\\ of\\ search\\ to\\ be\\ performed\\.", "css:finder"], + ["xpath=//button[@id='info-Indicates the type of search to be performed.']", "xpath:attributes"], + ["xpath=//div[@id='root_entityAttributesFilterTarget-group']/div/fieldset/div/div/div/label/button", "xpath:idRelative"], + ["xpath=//fieldset/div/div/div/label/button", "xpath:position"] + ], + "value": "30000" + }, { + "id": "e2b1a582-a3ce-4adb-b10c-0804f44a56ae", + "comment": "", + "command": "mouseOver", + "target": "id=info-Indicates the type of search to be performed.", + "targets": [ + ["id=info-Indicates the type of search to be performed.", "id"], + ["css=#info-Indicates\\ the\\ type\\ of\\ search\\ to\\ be\\ performed\\.", "css:finder"], + ["xpath=//button[@id='info-Indicates the type of search to be performed.']", "xpath:attributes"], + ["xpath=//div[@id='root_entityAttributesFilterTarget-group']/div/fieldset/div/div/div/label/button", "xpath:idRelative"], + ["xpath=//fieldset/div/div/div/label/button", "xpath:position"] + ], + "value": "" + }, { + "id": "c42fe40a-496c-491b-834e-694181da46d1", + "comment": "", + "command": "assertText", + "target": "css=div[role=\"tooltip\"]", + "targets": [], + "value": "Indicates the type of search to be performed." }, { "id": "e4596fdd-7ffc-4636-95d3-870d6bd51f20", "comment": "", @@ -1221,9 +1398,13 @@ "id": "583b61c9-b021-452b-b488-1faf2be311b6", "comment": "", "command": "mouseOver", - "target": "css=.row:nth-child(9) path:nth-child(1)", + "target": "id=info-tooltip.turn-off-encryption", "targets": [ - ["css=.row:nth-child(9) path:nth-child(1)", "css:finder"] + ["id=info-tooltip.turn-off-encryption", "id"], + ["css=#info-tooltip\\.turn-off-encryption", "css:finder"], + ["xpath=//button[@id='info-tooltip.turn-off-encryption']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_turnOffEncryption-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//div[9]/div/div/div/div/div/label/span/button", "xpath:position"] ], "value": "" }, { @@ -1233,6 +1414,13 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "Whether to turn off encryption of the response." + }, { + "id": "8c62c21b-b432-44ff-8823-76eb796373ab", + "comment": "", + "command": "click", + "target": "css=body", + "targets": [], + "value": "" }, { "id": "62691575-441e-4251-afd8-658fe8763578", "comment": "", @@ -1247,35 +1435,36 @@ ], "value": "label=NameIDFormat" }, { - "id": "47c3d355-4218-456a-9441-2d5d2ffa4d34", + "id": "a137db34-14ad-46dd-991e-2dcebd97805c", "comment": "", - "command": "waitForElementVisible", - "target": "css=.ms-2 > path", + "command": "pause", + "target": "5000", "targets": [], - "value": "30000" + "value": "" }, { - "id": "a6dcb984-5ad2-4004-8129-931adfc54a87", + "id": "1502c7aa-ec57-42ed-99b8-2071f5a653d2", "comment": "", - "command": "mouseOver", - "target": "css=.ms-2 > path", + "command": "waitForElementVisible", + "target": "id=info-tooltip.usa-sha-algorithm", "targets": [ - ["css=.ms-2 > path", "css:finder"] + ["id=info-tooltip.usa-sha-algorithm", "id"], + ["css=#info-tooltip\\.usa-sha-algorithm", "css:finder"], + ["xpath=//button[@id='info-tooltip.usa-sha-algorithm']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_useSha-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//span/button", "xpath:position"] ], - "value": "" - }, { - "id": "cda8d83d-d242-4def-b4cf-f696864fa806", - "comment": "", - "command": "mouseOut", - "target": "css=.ms-2 > path", - "targets": [], - "value": "" + "value": "30000" }, { "id": "b358a7c0-87a5-4b74-8013-c0ab379278c2", "comment": "", "command": "mouseOver", - "target": "css=.ms-2 > path", + "target": "id=info-tooltip.usa-sha-algorithm", "targets": [ - ["css=.ms-2 > path", "css:finder"] + ["id=info-tooltip.usa-sha-algorithm", "id"], + ["css=#info-tooltip\\.usa-sha-algorithm", "css:finder"], + ["xpath=//button[@id='info-tooltip.usa-sha-algorithm']", "xpath:attributes"], + ["xpath=//div[@id='root_relyingPartyOverrides_useSha-group']/div/div/div/label/span/button", "xpath:idRelative"], + ["xpath=//span/button", "xpath:position"] ], "value": "" }, { @@ -1302,9 +1491,13 @@ "id": "8657a3bf-3547-456a-8802-a23eaf657e7c", "comment": "", "command": "mouseOver", - "target": "css=.btn-text:nth-child(1) > .svg-inline--fa", + "target": "id=info-The value used to search against, such as a regex pattern or entityID to match against.", "targets": [ - ["css=.btn-text:nth-child(1) > .svg-inline--fa", "css:finder"] + ["id=info-The value used to search against, such as a regex pattern or entityID to match against.", "id"], + ["css=#info-The\\ value\\ used\\ to\\ search\\ against\\,\\ such\\ as\\ a\\ regex\\ pattern\\ or\\ entityID\\ to\\ match\\ against\\.", "css:finder"], + ["xpath=//button[@id='info-The value used to search against, such as a regex pattern or entityID to match against.']", "xpath:attributes"], + ["xpath=//div[@id='root_entityAttributesFilterTarget-group']/div/fieldset/div/div/div[2]/label/span[2]/button", "xpath:idRelative"], + ["xpath=//span[2]/button", "xpath:position"] ], "value": "" }, { @@ -1338,9 +1531,13 @@ "id": "f65720ad-5b7b-4193-a8d6-3ca976d2c976", "comment": "", "command": "mouseOver", - "target": "css=.btn-text:nth-child(1) path", + "target": "id=info-The value used to search against, such as a regex pattern or entityID to match against.", "targets": [ - ["css=.btn-text:nth-child(1) path", "css:finder"] + ["id=info-The value used to search against, such as a regex pattern or entityID to match against.", "id"], + ["css=#info-The\\ value\\ used\\ to\\ search\\ against\\,\\ such\\ as\\ a\\ regex\\ pattern\\ or\\ entityID\\ to\\ match\\ against\\.", "css:finder"], + ["xpath=//button[@id='info-The value used to search against, such as a regex pattern or entityID to match against.']", "xpath:attributes"], + ["xpath=//div[@id='root_entityAttributesFilterTarget-group']/div/fieldset/div/div/div[2]/label/span[2]/button", "xpath:idRelative"], + ["xpath=//span[2]/button", "xpath:position"] ], "value": "" }, { @@ -1374,9 +1571,13 @@ "id": "295c441f-78c8-4949-9c0c-c9c6202c8943", "comment": "", "command": "mouseOver", - "target": "css=.btn-text:nth-child(1) path", + "target": "id=info-The value used to search against, such as a regex pattern or entityID to match against.", "targets": [ - ["css=.btn-text:nth-child(1) path", "css:finder"] + ["id=info-The value used to search against, such as a regex pattern or entityID to match against.", "id"], + ["css=#info-The\\ value\\ used\\ to\\ search\\ against\\,\\ such\\ as\\ a\\ regex\\ pattern\\ or\\ entityID\\ to\\ match\\ against\\.", "css:finder"], + ["xpath=//button[@id='info-The value used to search against, such as a regex pattern or entityID to match against.']", "xpath:attributes"], + ["xpath=//div[@id='root_entityAttributesFilterTarget-group']/div/fieldset/div/div/div[2]/label/span[2]/button", "xpath:idRelative"], + ["xpath=//span[2]/button", "xpath:position"] ], "value": "" }, { diff --git a/backend/src/integration/resources/SHIBUI-1674-3.side b/backend/src/integration/resources/SHIBUI-1674-3.side index fa0a6f0cd..1b4bb29ba 100644 --- a/backend/src/integration/resources/SHIBUI-1674-3.side +++ b/backend/src/integration/resources/SHIBUI-1674-3.side @@ -133,9 +133,14 @@ "id": "6a47d0aa-0afe-4e61-afbf-44a56507a2e8", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(1) > div > div > .form-label > .btn path", + "target": "id=info-tooltip.entity-attribute-name", "targets": [ - ["css=.mb-3:nth-child(1) > div > div > .form-label > .btn path", "css:finder"] + ["id=info-tooltip.entity-attribute-name", "id"], + ["css=#info-tooltip\\.entity-attribute-name", "css:finder"], + ["xpath=//button[@id='info-tooltip.entity-attribute-name']", "xpath:attributes"], + ["xpath=//div[@id='root_name-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { @@ -145,15 +150,6 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "Name of the attribute that the service provider uses and requires from the identity provider. It corresponds to the element in the SAML assertion." - }, { - "id": "462beca1-71e3-4519-a478-f4e66824c3cc", - "comment": "", - "command": "mouseOut", - "target": "css=.mb-3:nth-child(1) > div > div > .form-label > .btn path", - "targets": [ - ["css=.mb-3:nth-child(1) > div > div > .form-label > .btn path", "css:finder"] - ], - "value": "" }, { "id": "1d0954f7-b5f4-4c6d-9e4f-90d1557ff57f", "comment": "", @@ -172,9 +168,13 @@ "id": "d316301e-1e75-4c95-bc8a-efa5575b3cfb", "comment": "", "command": "mouseOver", - "target": "css=.mb-3 > .form-label > .btn > .svg-inline--fa", + "target": "id=info-tooltip.entity-attribute-type", "targets": [ - ["css=.mb-3 > .form-label > .btn > .svg-inline--fa", "css:finder"] + ["id=info-tooltip.entity-attribute-type", "id"], + ["css=#info-tooltip\\.entity-attribute-type", "css:finder"], + ["xpath=//button[@id='info-tooltip.entity-attribute-type']", "xpath:attributes"], + ["xpath=//div[@id='root_attributeType-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[2]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -184,13 +184,6 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "Data type of the attribute such as boolean or string." - }, { - "id": "f2335699-a48b-4da0-906a-6b41fac18795", - "comment": "", - "command": "mouseOut", - "target": "css=.mb-3 > .form-label > .btn > .svg-inline--fa", - "targets": [], - "value": "" }, { "id": "15ab5915-5ee7-4942-9417-382d7171872f", "comment": "", @@ -209,9 +202,13 @@ "id": "9d4d25d7-3f22-4ca9-976b-fde51c951f44", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(3) .btn > .svg-inline--fa", + "target": "id=info-tooltip.entity-attribute-friendly-name", "targets": [ - ["css=.mb-3:nth-child(3) .btn > .svg-inline--fa", "css:finder"] + ["id=info-tooltip.entity-attribute-friendly-name", "id"], + ["css=#info-tooltip\\.entity-attribute-friendly-name", "css:finder"], + ["xpath=//button[@id='info-tooltip.entity-attribute-friendly-name']", "xpath:attributes"], + ["xpath=//div[@id='root_attributeFriendlyName-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[3]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -246,9 +243,13 @@ "id": "6b365142-29d2-4d6f-a3ff-bcc06f8102ac", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(4) .btn path", + "target": "id=info-tooltip.entity-attribute-attr-name", "targets": [ - ["css=.mb-3:nth-child(4) .btn path", "css:finder"] + ["id=info-tooltip.entity-attribute-attr-name", "id"], + ["css=#info-tooltip\\.entity-attribute-attr-name", "css:finder"], + ["xpath=//button[@id='info-tooltip.entity-attribute-attr-name']", "xpath:attributes"], + ["xpath=//div[@id='root_attributeName-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[4]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -258,13 +259,6 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "Indicates how to interpret the attribute name. It corresponds to the element in the SAML assertion. This is normally a uri or urn." - }, { - "id": "bb6a3d64-d0a9-43ab-b60c-20dab1f04e15", - "comment": "", - "command": "mouseOut", - "target": "css=.mb-3:nth-child(4) .btn path", - "targets": [], - "value": "" }, { "id": "5ed8705f-fc06-43c4-8fb0-2200f2b17417", "comment": "", @@ -283,9 +277,13 @@ "id": "4d5c9b81-fa8b-4202-9617-9e66b11c6453", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(5) .btn path", + "target": "id=info-tooltip.entity-attribute-display-name", "targets": [ - ["css=.mb-3:nth-child(5) .btn path", "css:finder"] + ["id=info-tooltip.entity-attribute-display-name", "id"], + ["css=#info-tooltip\\.entity-attribute-display-name", "css:finder"], + ["xpath=//button[@id='info-tooltip.entity-attribute-display-name']", "xpath:attributes"], + ["xpath=//div[@id='root_displayName-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[5]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -295,13 +293,6 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "Provides a human readable value that identifies the subject. This value is not guaranteed to be unique and is designed to be used only for display purposes." - }, { - "id": "535899ed-dfc1-4e6d-98bf-1d798eba2653", - "comment": "", - "command": "mouseOut", - "target": "css=.mb-3:nth-child(5) .btn path", - "targets": [], - "value": "" }, { "id": "dd9c55ae-367a-4dc6-acd3-9a4fc70af3c8", "comment": "", @@ -320,9 +311,13 @@ "id": "5e69982a-5611-4276-9145-b2ae74cafc5e", "comment": "", "command": "mouseOver", - "target": "css=.mb-3:nth-child(6) path", + "target": "id=info-tooltip.entity-attribute-help", "targets": [ - ["css=.mb-3:nth-child(6) path", "css:finder"] + ["id=info-tooltip.entity-attribute-help", "id"], + ["css=#info-tooltip\\.entity-attribute-help", "css:finder"], + ["xpath=//button[@id='info-tooltip.entity-attribute-help']", "xpath:attributes"], + ["xpath=//div[@id='root_helpText-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//div[6]/div/div/label/button", "xpath:position"] ], "value": "" }, { @@ -332,13 +327,6 @@ "target": "css=div[role=\"tooltip\"]", "targets": [], "value": "Defines help text used in the Shibboleth IDP UI when adding the attribute." - }, { - "id": "1903d80f-cd05-4b27-a8ee-7450f6ddfb1f", - "comment": "", - "command": "mouseOut", - "target": "css=.mb-3:nth-child(6) path", - "targets": [], - "value": "" }, { "id": "8d9f307d-d310-4fec-91d8-2d228bf07328", "comment": "", @@ -402,9 +390,14 @@ "id": "ca5dd037-f651-49bc-ae8e-2c4892a8dd8a", "comment": "", "command": "mouseOver", - "target": "css=.fa-circle-info", + "target": "id=info-tooltip.bundle-name", "targets": [ - ["css=.fa-circle-info", "css:finder"] + ["id=info-tooltip.bundle-name", "id"], + ["css=#info-tooltip\\.bundle-name", "css:finder"], + ["xpath=//button[@id='info-tooltip.bundle-name']", "xpath:attributes"], + ["xpath=//div[@id='root_name-group']/div/div/label/button", "xpath:idRelative"], + ["xpath=//label/button", "xpath:position"], + ["xpath=//button[contains(.,'Description')]", "xpath:innerText"] ], "value": "" }, { diff --git a/backend/src/integration/resources/SHIBUI-1732-3.side b/backend/src/integration/resources/SHIBUI-1732-3.side index ad3bec798..79e2f313a 100644 --- a/backend/src/integration/resources/SHIBUI-1732-3.side +++ b/backend/src/integration/resources/SHIBUI-1732-3.side @@ -461,15 +461,10 @@ }, { "id": "95c2701d-82d8-4d2d-b83e-82bb4bd2cf8c", "comment": "", - "command": "assertText", - "target": "css=.row:nth-child(5) .control-label", - "targets": [ - ["css=.row:nth-child(5) .control-label", "css:finder"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div[2]/div/div/form/div/div/div/div/div[8]/div/div/div/div[5]/div/div/div/div/div/div/span", "xpath:idRelative"], - ["xpath=//div[5]/div/div/div/div/div/div/span", "xpath:position"], - ["xpath=//span[contains(.,'Custom List Display')]", "xpath:innerText"] - ], - "value": "Custom List Display" + "command": "assertElementPresent", + "target": "//*[text()[contains(.,'Custom List Display')]]", + "targets": [], + "value": "" }, { "id": "d6d968f3-a549-4e0b-8fe0-0ad37d80fea7", "comment": "", @@ -636,11 +631,11 @@ "id": "63e0b87d-4da3-4fb8-aa9f-6412e0562709", "comment": "", "command": "waitForElementVisible", - "target": "css=.align-items-start:nth-child(7) > .p-2", + "target": "css=.align-items-start:nth-child(5) > .p-2", "targets": [ - ["css=.align-items-start:nth-child(7) > .p-2", "css:finder"], - ["xpath=//div[@id='root']/div/main/div/section/div/div/section[7]/div/div[2]/div[2]/div/div[7]/span", "xpath:idRelative"], - ["xpath=//div[7]/span", "xpath:position"], + ["css=.align-items-start:nth-child(5) > .p-2", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div/div/section[7]/div/div[2]/div[2]/div/div[5]/span", "xpath:idRelative"], + ["xpath=//div[5]/span", "xpath:position"], ["xpath=//span[contains(.,'Custom List Display')]", "xpath:innerText"] ], "value": "30000" @@ -1220,7 +1215,8 @@ "targets": [ ["css=.d-flex:nth-child(5) .d-block", "css:finder"], ["xpath=//div[@id='filters']/ul/li/div[2]/section[2]/div/div[2]/div[2]/div/div[5]/ul/li/span", "xpath:idRelative"], - ["xpath=//div[5]/ul/li/span", "xpath:position"] + ["xpath=//div[5]/ul/li/span", "xpath:position"], + ["xpath=//span[contains(.,'bar')]", "xpath:innerText"] ], "value": "bar" }, { @@ -1300,11 +1296,12 @@ "id": "83aa335b-5efb-4ca2-9e41-f06213cc68e2", "comment": "", "command": "assertText", - "target": "css=.d-flex:nth-child(5) .d-block", + "target": "css=.d-flex:nth-child(5) .d-flex:nth-child(1) > .d-block", "targets": [ - ["css=.d-flex:nth-child(5) .d-block", "css:finder"], + ["css=.d-flex:nth-child(5) .d-flex:nth-child(1) > .d-block", "css:finder"], ["xpath=//div[@id='filters']/ul/li/div[2]/section[2]/div/div[2]/div[2]/div/div[5]/ul/li/span", "xpath:idRelative"], - ["xpath=//div[5]/ul/li/span", "xpath:position"] + ["xpath=//div[5]/ul/li/span", "xpath:position"], + ["xpath=//span[contains(.,'bar')]", "xpath:innerText"] ], "value": "bar" }, { @@ -1314,8 +1311,8 @@ "target": "css=.d-flex:nth-child(2) > .d-block", "targets": [ ["css=.d-flex:nth-child(2) > .d-block", "css:finder"], - ["xpath=//div[@id='filters']/ul/li/div[2]/section[2]/div/div[2]/div[2]/div/div[7]/ul/li[2]/span", "xpath:idRelative"], - ["xpath=//ul/li[2]/span", "xpath:position"] + ["xpath=//div[@id='filters']/ul/li/div[2]/section[2]/div/div[2]/div[2]/div/div[5]/ul/li[2]/span", "xpath:idRelative"], + ["xpath=//li[2]/span", "xpath:position"] ], "value": "foo" }, { diff --git a/backend/src/integration/resources/SHIBUI-1744-3.side b/backend/src/integration/resources/SHIBUI-1744-3.side index 67e45e747..a683a9ae6 100644 --- a/backend/src/integration/resources/SHIBUI-1744-3.side +++ b/backend/src/integration/resources/SHIBUI-1744-3.side @@ -259,6 +259,13 @@ ["xpath=//span[contains(.,'2. Common Attributes')]", "xpath:innerText"] ], "value": "" + }, { + "id": "1a12b13b-c746-4dec-ae20-de15e29de90b", + "comment": "", + "command": "waitForElementEditable", + "target": "id=root_xmlId", + "targets": [], + "value": "30000" }, { "id": "7cfab6d3-3982-423f-a99c-488e1d41e40b", "comment": "", @@ -375,6 +382,13 @@ "target": "name=type", "targets": [], "value": "label=EntityAttributes" + }, { + "id": "81b2902f-da13-4e60-ba15-be83fb1f24a0", + "comment": "", + "command": "waitForElementEditable", + "target": "id=root_name", + "targets": [], + "value": "30000" }, { "id": "208a221d-db9c-4fa7-8116-24b5f425fc11", "comment": "", @@ -454,28 +468,38 @@ "id": "e8a2f501-0efa-43a3-bbf2-b86e5ec8f6d1", "comment": "", "command": "waitForElementVisible", - "target": "css=fieldset > div > button > strong", - "targets": [], + "target": "id=user-attr-bundle-btn-0", + "targets": [ + ["id=user-attr-bundle-btn-0", "id"], + ["css=#user-attr-bundle-btn-0", "css:finder"], + ["xpath=//button[@id='user-attr-bundle-btn-0']", "xpath:attributes"], + ["xpath=//div[@id='root_attributeRelease-group']/div/fieldset/div/button", "xpath:idRelative"], + ["xpath=//fieldset/div/button", "xpath:position"] + ], "value": "30000" }, { "id": "cc36c580-3d53-483a-a813-2a6c8fb95792", "comment": "", "command": "assertText", - "target": "css=fieldset > div > button > strong", + "target": "css=strong", "targets": [ ["css=strong", "css:finder"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div/div[2]/div[2]/div[2]/div/form/div/div/div/div[8]/div/div/div/fieldset/ul/li/strong", "xpath:idRelative"], + ["xpath=//button[@id='user-attr-bundle-btn-0']/strong", "xpath:idRelative"], ["xpath=//strong", "xpath:position"], ["xpath=//strong[contains(.,'Bundle - Test Bundle')]", "xpath:innerText"] ], "value": "Bundle - Test Bundle" }, { - "id": "caabc3c1-7ac7-4251-9885-fe2e3b7c4c10", + "id": "f4b2e9ae-cb20-4c41-8ad6-50004e75a865", "comment": "", "command": "click", - "target": "css=fieldset > div > button", + "target": "id=user-attr-bundle-btn-0", "targets": [ - ["css=.fa-check:nth-child(2) > path", "css:finder"] + ["id=user-attr-bundle-btn-0", "id"], + ["css=#user-attr-bundle-btn-0", "css:finder"], + ["xpath=//button[@id='user-attr-bundle-btn-0']", "xpath:attributes"], + ["xpath=//div[@id='root_attributeRelease-group']/div/fieldset/div/button", "xpath:idRelative"], + ["xpath=//fieldset/div/button", "xpath:position"] ], "value": "" }, { @@ -677,21 +701,21 @@ ["xpath=//section[3]/div/div[2]/div[2]/div[5]/div/span", "xpath:position"] ], "value": "true" - },{ - "id": "4ec2c493-85e4-403b-9b09-031c5728f498", - "comment": "", - "command": "open", - "target": "/api/heheheheheheheWipeout", - "targets": [], - "value": "" - }, { - "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", - "comment": "", - "command": "assertText", - "target": "css=body", - "targets": [], - "value": "yes, you did it" - }] + }, { + "id": "4ec2c493-85e4-403b-9b09-031c5728f498", + "comment": "", + "command": "open", + "target": "/api/heheheheheheheWipeout", + "targets": [], + "value": "" + }, { + "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4", + "comment": "", + "command": "assertText", + "target": "css=body", + "targets": [], + "value": "yes, you did it" + }] }], "suites": [{ "id": "56040c8b-b45c-454e-b2ba-2a06056b397f", diff --git a/backend/src/integration/resources/SHIBUI-2269.side b/backend/src/integration/resources/SHIBUI-2269.side index 3b2265392..e8881bec7 100644 --- a/backend/src/integration/resources/SHIBUI-2269.side +++ b/backend/src/integration/resources/SHIBUI-2269.side @@ -212,7 +212,7 @@ ], "value": "" }, { - "id": "836b08a5-1a08-4482-aff2-455fb02f3eb6", + "id": "033008de-7ccc-49ae-a944-a116364c3992", "comment": "", "command": "waitForElementEditable", "target": "id=enable-switch-0", @@ -225,7 +225,7 @@ ], "value": "30000" }, { - "id": "29aa153e-4f7d-4468-bbef-db0b3f9de6eb", + "id": "ccbdf33d-b35d-44d3-93c2-1198329df520", "comment": "", "command": "click", "target": "id=enable-switch-0", @@ -238,10 +238,10 @@ ], "value": "" }, { - "id": "4753e8d9-eca2-4616-879b-64c6308c2b94", + "id": "c7660056-bb11-4d81-bfc1-30f04a4f1efd", "comment": "", "command": "waitForElementVisible", - "target": "css=.alert", + "target": "css=.show", "targets": [], "value": "30000" }, { @@ -325,11 +325,11 @@ "id": "d01e44ab-802b-4d85-ac2e-e2e03a00c1c0", "comment": "", "command": "click", - "target": "css=.dropdown-item:nth-child(3)", + "target": "css=.show > .dropdown-item:nth-child(3)", "targets": [ - ["css=.dropdown-item:nth-child(3)", "css:finder"], + ["css=.show > .dropdown-item:nth-child(3)", "css:finder"], ["xpath=(//button[@type='button'])[17]", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div/div[2]/div[2]/div[2]/div/form/div/div/div/div[6]/div/div/div/fieldset/div/div/div/div/div/button[3]", "xpath:idRelative"], + ["xpath=//div[@id='root_entityAttributesFilterTarget-group']/div/fieldset/div/div/div/div/div/button[3]", "xpath:idRelative"], ["xpath=//div/button[3]", "xpath:position"], ["xpath=//button[contains(.,'Script')]", "xpath:innerText"] ], @@ -428,11 +428,11 @@ "id": "bce0cd29-246e-4f6e-a860-0eade5c73850", "comment": "", "command": "click", - "target": "css=.dropdown-item:nth-child(3)", + "target": "css=.show > .dropdown-item:nth-child(3)", "targets": [ - ["css=.dropdown-item:nth-child(3)", "css:finder"], + ["css=.show > .dropdown-item:nth-child(3)", "css:finder"], ["xpath=(//button[@type='button'])[16]", "xpath:attributes"], - ["xpath=//div[@id='root']/div/main/div/section/div[2]/div/div[2]/div[2]/div[2]/div/form/div/div/div/div[3]/div/div/div/fieldset/div/div/div/div/div/button[3]", "xpath:idRelative"], + ["xpath=//div[@id='root_nameIdFormatFilterTarget-group']/div/fieldset/div/div/div/div/div/button[3]", "xpath:idRelative"], ["xpath=//button[3]", "xpath:position"], ["xpath=//button[contains(.,'Script')]", "xpath:innerText"] ], @@ -487,6 +487,31 @@ ["css=.fa-floppy-disk > path", "css:finder"] ], "value": "" + }, { + "id": "802be014-0d04-4bda-93d1-ca7a5d7f802d", + "comment": "", + "command": "waitForElementVisible", + "target": "css=div:nth-child(5) .text-truncate", + "targets": [ + ["css=div:nth-child(5) .text-truncate", "css:finder"], + ["xpath=//div[@id='root']/div/main/div/section/div/div/section/div/div[2]/div[2]/div[5]/div/span[2]", "xpath:idRelative"], + ["xpath=//div[5]/div/span[2]", "xpath:position"], + ["xpath=//span[contains(.,'External Test')]", "xpath:innerText"] + ], + "value": "30000" + }, { + "id": "7e3e7d65-e0ff-4a2d-a0e9-5080bfefd4df", + "comment": "", + "command": "click", + "target": "css=div:nth-child(1) > .btn:nth-child(2)", + "targets": [ + ["css=div:nth-child(1) > .btn:nth-child(2)", "css:finder"], + ["xpath=(//button[@type='button'])[5]", "xpath:attributes"], + ["xpath=//div[@id='navigation']/div/button", "xpath:idRelative"], + ["xpath=//div[2]/div/button", "xpath:position"], + ["xpath=//button[contains(.,' Filters')]", "xpath:innerText"] + ], + "value": "" }, { "id": "42929ec9-7860-467a-a52b-946df9965de5", "comment": "", diff --git a/backend/src/integration/resources/SHIBUI-2380.side b/backend/src/integration/resources/SHIBUI-2380.side index a81cad5c3..ac159d3a3 100644 --- a/backend/src/integration/resources/SHIBUI-2380.side +++ b/backend/src/integration/resources/SHIBUI-2380.side @@ -1381,14 +1381,14 @@ "id": "eb9c18d9-d0fc-4723-840b-7be59434bebf", "comment": "", "command": "waitForElementVisible", - "target": "css=.alert", + "target": "css=.show", "targets": [], "value": "30000" }, { "id": "f27630e9-561e-49f6-b557-b2c7ff0647fc", "comment": "", "command": "assertText", - "target": "css=.alert", + "target": "css=.show", "targets": [], "value": "Metadata source has been deleted." }, { diff --git a/backend/src/main/resources/application.yml b/backend/src/main/app-resources/default.yml similarity index 92% rename from backend/src/main/resources/application.yml rename to backend/src/main/app-resources/default.yml index bbe104c23..08fdfb387 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/app-resources/default.yml @@ -35,7 +35,7 @@ custom: attributes: - # Default attributes + # Default attributes - name: eduPersonPrincipalName displayName: label.attribute-eduPersonPrincipalName - name: uid @@ -62,26 +62,26 @@ custom: displayName: label.attribute-employeeNumber # Custom attributes -# The following contains a map of "relying party overrides". -# The structure of an entry is as follows: -# - name: The name of the entry. used to uniquely identify this entry. -# displayName: This will normally be the label used when displaying this override in the UI -# displayType: The type to use when displaying this option -# helpText: This is the help-icon hover-over text -# defaultValues: One or more values to be displayed as default options in the UI -# persistType: Optional. If it is necessary to persist something different than the override's display type, -# set that type here. For example, display a boolean, but persist a string. -# persistValue: Required only when persistType is used. Defines the value to be persisted. -# attributeName: This is the name of the attribute to be used in the xml. This is assumed to be a URI. -# attributeFriendlyName: This is the friendly name associated with the above attributeName. -# -# It is imperative when defining these that the "displayType" and "persistType" are known types. -# Typos or unsupported values here will result in that override being skipped! -# Supported types are as follows: boolean, integer, string, set, list -# Note that "persistType" doesn't have to match "displayType". However, the only unmatching combination currently -# supported is a "displayType" of "boolean" and "persistType" of "string". + # The following contains a map of "relying party overrides". + # The structure of an entry is as follows: + # - name: The name of the entry. used to uniquely identify this entry. + # displayName: This will normally be the label used when displaying this override in the UI + # displayType: The type to use when displaying this option + # helpText: This is the help-icon hover-over text + # defaultValues: One or more values to be displayed as default options in the UI + # persistType: Optional. If it is necessary to persist something different than the override's display type, + # set that type here. For example, display a boolean, but persist a string. + # persistValue: Required only when persistType is used. Defines the value to be persisted. + # attributeName: This is the name of the attribute to be used in the xml. This is assumed to be a URI. + # attributeFriendlyName: This is the friendly name associated with the above attributeName. + # + # It is imperative when defining these that the "displayType" and "persistType" are known types. + # Typos or unsupported values here will result in that override being skipped! + # Supported types are as follows: boolean, integer, string, set, list + # Note that "persistType" doesn't have to match "displayType". However, the only unmatching combination currently + # supported is a "displayType" of "boolean" and "persistType" of "string". overrides: - # Default overrides + # Default overrides - name: signAssertion displayName: label.sign-the-assertion displayType: boolean diff --git a/backend/src/main/app-resources/release.yml b/backend/src/main/app-resources/release.yml new file mode 100644 index 000000000..006b05506 --- /dev/null +++ b/backend/src/main/app-resources/release.yml @@ -0,0 +1,452 @@ +#spring: +# jpa: +# show-sql: false +# properties: +# hibernate: +# format_sql: true +# dialect: org.hibernate.dialect.PostgreSQL95Dialect +# OR SEE: https://access.redhat.com/webassets/avalon/d/red-hat-jboss-enterprise-application-platform/7.2/javadocs/org/hibernate/dialect/package-summary.html + +#shibui: +## Default password must be set for the default user to be configured and setup +# default-rootuser:root +## need to include the encoding for the password - be sure to quote the entire value as shown +# default-password: "{noop}foopassword" +# pac4j-enabled: true +# pac4j: +# keystorePath: "/etc/shibui/samlKeystore.jks" +# keystorePassword: "changeit" +# privateKeyPassword: "changeit" +# serviceProviderEntityId: "https://idp.example.com/shibui" +# serviceProviderMetadataPath: "/etc/shibui/sp-metadata.xml" +# identityProviderMetadataPath: "/etc/shibui/idp-metadata.xml" +# forceServiceProviderMetadataGeneration: false +# callbackUrl: "https://localhost:8443/callback" +# postLogoutURL: "https://idp.example.com/idp/profile/Logout" # Must set this to get IDP logout +# maximumAuthenticationLifetime: 3600000 +# requireAssertedRoleForNewUsers: false +# saml2ProfileMapping: +# username: urn:oid:0.9.2342.19200300.100.1.1 +# firstname: urn:oid:2.5.4.42 +# lastname: urn:oid:2.5.4.4 +# email: urn:oid:0.9.2342.19200300.100.1.3 +# groups: urn:oid:1.3.6.1.4.1.5923.1.5.1.1 # attributeId - isMemberOf +# roles: --define name of the attribute containing the incoming user roles-- + +custom: + attributes: + # Default attributes + - name: eduPersonPrincipalName + displayName: label.attribute-eduPersonPrincipalName + - name: uid + displayName: label.attribute-uid + - name: mail + displayName: label.attribute-mail + - name: surname + displayName: label.attribute-surname + - name: givenName + displayName: label.attribute-givenName + - name: eduPersonAffiliation + displayName: label.attribute-eduPersonAffiliation + - name: eduPersonScopedAffiliation + displayName: label.attribute-eduPersonScopedAffiliation + - name: eduPersonPrimaryAffiliation + displayName: label.attribute-eduPersonPrimaryAffiliation + - name: eduPersonEntitlement + displayName: label.attribute-eduPersonEntitlement + - name: eduPersonAssurance + displayName: label.attribute-eduPersonAssurance + - name: eduPersonUniqueId + displayName: label.attribute-eduPersonUniqueId + - name: employeeNumber + displayName: label.attribute-employeeNumber + # Custom attributes + + # The following contains a map of "relying party overrides". + # The structure of an entry is as follows: + # - name: The name of the entry. used to uniquely identify this entry. + # displayName: This will normally be the label used when displaying this override in the UI + # displayType: The type to use when displaying this option + # helpText: This is the help-icon hover-over text + # defaultValues: One or more values to be displayed as default options in the UI + # persistType: Optional. If it is necessary to persist something different than the override's display type, + # set that type here. For example, display a boolean, but persist a string. + # persistValue: Required only when persistType is used. Defines the value to be persisted. + # attributeName: This is the name of the attribute to be used in the xml. This is assumed to be a URI. + # attributeFriendlyName: This is the friendly name associated with the above attributeName. + # + # It is imperative when defining these that the "displayType" and "persistType" are known types. + # Typos or unsupported values here will result in that override being skipped! + # Supported types are as follows: boolean, integer, string, set, list + # Note that "persistType" doesn't have to match "displayType". However, the only unmatching combination currently + # supported is a "displayType" of "boolean" and "persistType" of "string". + overrides: + # Default overrides + - name: signAssertion + displayName: label.sign-the-assertion + displayType: boolean + helpText: tooltip.sign-assertion + attributeName: http://shibboleth.net/ns/profiles/saml2/sso/browser/signAssertions + attributeFriendlyName: signAssertions + - name: dontSignResponse + displayName: label.dont-sign-the-response + displayType: boolean + helpText: tooltip.dont-sign-response + attributeName: http://shibboleth.net/ns/profiles/saml2/sso/browser/signResponses + attributeFriendlyName: signResponses + invert: true + - name: turnOffEncryption + displayName: label.turn-off-encryption-of-response + displayType: boolean + helpText: tooltip.turn-off-encryption + attributeName: http://shibboleth.net/ns/profiles/encryptAssertions + attributeFriendlyName: encryptAssertions + invert: true + - name: useSha + displayName: label.use-sha1-signing-algorithm + displayType: boolean + helpText: tooltip.usa-sha-algorithm + persistType: string + persistValue: shibboleth.SecurityConfiguration.SHA1 + attributeName: http://shibboleth.net/ns/profiles/securityConfiguration + attributeFriendlyName: securityConfiguration + protocol: saml,oidc + - name: ignoreAuthenticationMethod + displayName: label.ignore-any-sp-requested-authentication-method + displayType: boolean + helpText: tooltip.ignore-auth-method + persistType: string + persistValue: 0x1 + attributeName: http://shibboleth.net/ns/profiles/disallowedFeatures + attributeFriendlyName: disallowedFeatures + protocol: saml,oidc + - name: omitNotBefore + displayName: label.omit-not-before-condition + displayType: boolean + helpText: tooltip.omit-not-before-condition + attributeName: http://shibboleth.net/ns/profiles/includeConditionsNotBefore + attributeFriendlyName: includeConditionsNotBefore + invert: true + - name: responderId + displayName: label.responder-id + displayType: string + helpText: tooltip.responder-id + attributeName: http://shibboleth.net/ns/profiles/responderId + attributeFriendlyName: responderId + - name: nameIdFormats + displayName: label.nameid-format-to-send + displayType: set + helpText: tooltip.nameid-format + defaultValues: + - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified + - urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + - urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + - urn:oasis:names:tc:SAML:2.0:nameid-format:transient + attributeName: http://shibboleth.net/ns/profiles/nameIDFormatPrecedence + attributeFriendlyName: nameIDFormatPrecedence + - name: authenticationMethods + displayName: label.authentication-methods-to-use + displayType: set + helpText: tooltip.authentication-methods-to-use + defaultValues: + - https://refeds.org/profile/mfa + - urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken + - urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport + attributeName: http://shibboleth.net/ns/profiles/defaultAuthenticationMethods + attributeFriendlyName: defaultAuthenticationMethods + protocol: saml,oidc + - name: forceAuthn + displayName: label.force-authn + displayType: boolean + helpText: tooltip.force-authn + attributeName: http://shibboleth.net/ns/profiles/forceAuthn + attributeFriendlyName: forceAuthn + - name: ignoreRequestSignatures + displayName: label.ignore-request-signatures + displayType: boolean + helpText: tooltip.ignore-request-signatures + attributeName: http://shibboleth.net/ns/profiles/ignoreRequestSignatures + attributeFriendlyName: ignoreRequestSignatures + - name: postAuthenticationFlows + attributeFriendlyName: postAuthenticationFlows + displayName: label.postAuthenticationFlows + helpText: tooltip.postAuthenticationFlows + displayType: selection_list + defaultValues: + - attribute-release + - expiring-password + - terms-of-use + attributeName: http://shibboleth.net/ns/profiles/postAuthenticationFlows" + protocol: saml,oidc + - name: inboundInterceptorFlows + attributeFriendlyName: inboundInterceptorFlows + displayName: label.inboundInterceptorFlows + helpText: tooltip.inboundInterceptorFlows + displayType: string + attributeName: http://shibboleth.net/ns/profiles/inboundInterceptorFlows + protocol: oidc + - name: outboundInterceptorFlows + attributeFriendlyName: outboundInterceptorFlows + displayName: label.outboundInterceptorFlows + helpText: tooltip.outboundInterceptorFlows + displayType: string + attributeName: http://shibboleth.net/ns/profiles/outboundInterceptorFlows + protocol: oidc + - name: tokenEndpointAuthMethods + attributeFriendlyName: tokenEndpointAuthMethods + displayName: label.tokenEndpointAuthMethods + helpText: tooltip.tokenEndpointAuthMethods + displayType: string + defaultValue: client_secret_basic, client_secret_post, client_secret_jwt, private_key_jwt + attributeName: http://shibboleth.net/ns/profiles/tokenEndpointAuthMethods + protocol: oidc + - name: proxyCount + attributeFriendlyName: proxyCount + displayName: label.proxyCount + helpText: tooltip.proxyCount + displayType: integer + attributeName: http://shibboleth.net/ns/profiles/proxyCount + protocol: oidc + - name: revocationLifetime + attributeFriendlyName: revocationLifetime + displayName: label.revocationLifetime + helpText: tooltip.revocationLifetime + displayType: string + defaultValue: PT6H + attributeName: http://shibboleth.net/ns/profiles/oauth2/revocation/revocationLifetime + protocol: oidc + - name: revocationMethod + attributeFriendlyName: revocationMethod + displayName: label.revocationMethod + helpText: tooltip.revocationMethod + displayType: selection_list + defaultValues: + - CHAIN + - TOKEN + defaultValue: CHAIN + attributeName: http://shibboleth.net/ns/profiles/oauth2/revocation/revocationMethod + protocol: oidc + - name: accessTokenLifetimeOauth + attributeFriendlyName: accessTokenLifetime + displayName: label.accessTokenLifetime.oauth + helpText: tooltip.accessTokenLifetime.oauth + displayType: string + defaultValue: PT10M + attributeName: http://shibboleth.net/ns/profiles/oauth2/token/accessTokenLifetime + protocol: oidc + - name: accessTokenTypeOauth + attributeFriendlyName: accessTokenType + displayName: label.accessTokenType.oauth + helpText: tooltip.accessTokenType.oauth + displayType: string + attributeName: http://shibboleth.net/ns/profiles/oauth2/token/accessTokenType + protocol: oidc + - name: allowPKCEPlainOauth + attributeFriendlyName: allowPKCEPlainOauth + displayName: label.allowPKCEPlain.oauth + helpText: tooltip.allowPKCEPlain.oauth + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oauth2/token/allowPKCEPlain + protocol: oidc + - name: enforceRefreshTokenRotation + attributeFriendlyName: enforceRefreshTokenRotation + displayName: label.enforceRefreshTokenRotation + helpText: tooltip.enforceRefreshTokenRotation + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oauth2/token/enforceRefreshTokenRotation + protocol: oidc + - name: forcePKCEOauth + attributeFriendlyName: forcePKCEOauth + displayName: label.forcePKCE.oauth + helpText: tooltip.forcePKCE.oauth + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oauth2/token/forcePKCE + protocol: oidc + - name: grantTypes + attributeFriendlyName: grantTypes + displayName: label.grantTypes + helpText: tooltip.grantTypes + displayType: string + defaultValue: authorization_code, refresh_token + attributeName: http://shibboleth.net/ns/profiles/oauth2/token/grantTypes + protocol: oidc + - name: refreshTokenLifetimeOauth + attributeFriendlyName: refreshTokenLifetime + displayName: label.refreshTokenLifetime.oauth + helpText: tooltip.refreshTokenLifetime.oauth + displayType: string + defaultValue: PT2H + attributeName: http://shibboleth.net/ns/profiles/oauth2/token/refreshTokenLifetime + protocol: oidc + - name: resolveAttributesOauth + attributeFriendlyName: resolveAttributesOauth + displayName: label.resolveAttributes.oauth + helpText: tooltip.resolveAttributes.oauth + displayType: boolean + defaultValue: true + attributeName: http://shibboleth.net/ns/profiles/oauth2/token/resolveAttributes + protocol: oidc + - name: authorizationCodeFlowEnabled + attributeFriendlyName: authorizationCodeFlowEnabled + displayName: label.authorizationCodeFlowEnabled + helpText: tooltip.authorizationCodeFlowEnabled + displayType: boolean + defaultValue: true + attributeName: http://shibboleth.net/ns/profiles/authorizationCodeFlowEnabled + protocol: oidc + - name: hybridFlowEnabled + attributeFriendlyName: hybridFlowEnabled + displayName: label.hybridFlowEnabled + helpText: tooltip.hybridFlowEnabled + displayType: boolean + defaultValue: true + attributeName: http://shibboleth.net/ns/profiles/hybridFlowEnabled + protocol: oidc + - name: implicitFlowEnabled + attributeFriendlyName: implicitFlowEnabled + displayName: label.implicitFlowEnabled + helpText: tooltip.implicitFlowEnabled + displayType: boolean + defaultValue: true + attributeName: http://shibboleth.net/ns/profiles/implicitFlowEnabled + protocol: oidc + - name: refreshTokensEnabled + attributeFriendlyName: refreshTokensEnabled + displayName: label.refreshTokensEnabled + helpText: tooltip.refreshTokensEnabled + displayType: boolean + defaultValue: true + attributeName: http://shibboleth.net/ns/profiles/refreshTokensEnabled + protocol: oidc + - name: accessTokenLifetimeOidc + attributeFriendlyName: accessTokenLifetime + displayName: label.accessTokenLifetime.oidc + helpText: tooltip.accessTokenLifetime.oidc + displayType: string + defaultValue: PT10M + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/accessTokenLifetime + protocol: oidc + - name: accessTokenTypeOidc + attributeFriendlyName: accessTokenType + displayName: label.accessTokenType.oidc + helpText: tooltip.accessTokenType.oidc + displayType: string + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/accessTokenType + protocol: oidc + - name: acrRequestAlwaysEssential + attributeFriendlyName: acrRequestAlwaysEssential + displayName: label.acrRequestAlwaysEssential + helpText: tooltip.acrRequestAlwaysEssential + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/acrRequestAlwaysEssential + protocol: oidc + - name: allowPKCEPlainOidc + attributeFriendlyName: allowPKCEPlainOidc + displayName: label.allowPKCEPlain.oidc + helpText: tooltip.allowPKCEPlain.oidc + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/allowPKCEPlain + protocol: oidc + - name: alwaysIncludedAttributesBrowser + attributeFriendlyName: alwaysIncludedAttributes + displayName: label.alwaysIncludedAttributes.browser + helpText: tooltip.alwaysIncludedAttributes.browser + displayType: string + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/alwaysIncludedAttributes + protocol: oidc + - name: authorizeCodeLifetime + attributeFriendlyName: authorizeCodeLifetime + displayName: label.authorizeCodeLifetime + helpText: tooltip.authorizeCodeLifetime + displayType: string + defaultValue: PT5M + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/authorizeCodeLifetime + protocol: oidc + - name: deniedUserInfoAttributesBrowser + attributeFriendlyName: deniedUserInfoAttributes + displayName: label.deniedUserInfoAttributes.browser + helpText: tooltip.deniedUserInfoAttributes.browser + displayType: string + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/deniedUserInfoAttributes + protocol: oidc + - name: encodeConsentInTokens + attributeFriendlyName: encodeConsentInTokens + displayName: label.encodeConsentInTokens + helpText: tooltip.encodeConsentInTokens + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/encodeConsentInTokens + protocol: oidc + - name: encodedAttributes + attributeFriendlyName: encodedAttributes + displayName: label.encodedAttributes + helpText: tooltip.encodedAttributes + displayType: string + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/encodedAttributes + protocol: oidc + - name: forcePKCEOidc + attributeFriendlyName: forcePKCEOidc + displayName: label.forcePKCE.oidc + helpText: tooltip.forcePKCE.oidc + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/forcePKCE + protocol: oidc + - name: IDTokenLifetimeBrowser + attributeFriendlyName: IDTokenLifetimeBrowser + displayName: label.IDTokenLifetime.browser + helpText: tooltip.IDTokenLifetime.browser + displayType: string + defaultValue: PT1H + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/IDTokenLifetime + protocol: oidc + - name: includeIssuerInResponse + attributeFriendlyName: includeIssuerInResponse + displayName: label.includeIssuerInResponse + helpText: tooltip.includeIssuerInResponse + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/includeIssuerInResponse + protocol: oidc + - name: refreshTokenLifetimeOidc + attributeFriendlyName: refreshTokenLifetime + displayName: label.refreshTokenLifetime.oidc + helpText: tooltip.refreshTokenLifetime.oidc + displayType: string + defaultValue: PT2H + attributeName: http://shibboleth.net/ns/profiles/oidc/sso/browser/refreshTokenLifetime + protocol: oidc + - name: alwaysIncludedAttributesToken + attributeFriendlyName: alwaysIncludedAttributes + displayName: label.alwaysIncludedAttributes.token + helpText: tooltip.alwaysIncludedAttributes.token + displayType: string + attributeName: http://shibboleth.net/ns/profiles/oidc/token/alwaysIncludedAttributes + protocol: oidc + - name: encryptionOptional + attributeFriendlyName: encryptionOptional + displayName: label.encryptionOptional + helpText: tooltip.encryptionOptional + displayType: boolean + defaultValue: true + attributeName: http://shibboleth.net/ns/profiles/oidc/token/encryptionOptional + protocol: oidc + - name: IDTokenLifetime + attributeFriendlyName: IDTokenLifetime + displayName: label.IDTokenLifetime + helpText: tooltip.IDTokenLifetime + displayType: string + defaultValue: PT1H + attributeName: http://shibboleth.net/ns/profiles/oidc/token/IDTokenLifetime + protocol: oidc + - name: deniedUserInfoAttributes + attributeFriendlyName: deniedUserInfoAttributes + displayName: label.deniedUserInfoAttributes + helpText: tooltip.deniedUserInfoAttributes + displayType: string + attributeName: http://shibboleth.net/ns/profiles/oidc/userinfo/deniedUserInfoAttributes + protocol: oidc + - name: resolveAttributesOIDC + attributeFriendlyName: resolveAttributesOIDC + displayName: label.resolveAttributes.oidc + helpText: tooltip.resolveAttributes.oidc + displayType: boolean + attributeName: http://shibboleth.net/ns/profiles/oidc/userinfo/resolveAttributes + protocol: oidc \ No newline at end of file diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationUiDefinitionController.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationUiDefinitionController.groovy new file mode 100644 index 000000000..b2a75350b --- /dev/null +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationUiDefinitionController.groovy @@ -0,0 +1,58 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocationRegistry +import edu.internet2.tier.shibboleth.admin.ui.service.JsonSchemaBuilderService +import groovy.util.logging.Slf4j +import io.swagger.v3.oas.annotations.tags.Tag +import io.swagger.v3.oas.annotations.tags.Tags +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +import javax.annotation.PostConstruct + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.algorithmFilterSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.dynamicRegistrationSchema +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR + +/** + * Controller implementing REST resource responsible for exposing structure definition for dynamic registration user + * interface in terms of JSON schema. + */ +@RestController +@RequestMapping('/api/ui/DynamicRegistration') +@Slf4j +@Tags(value = [@Tag(name = "ui")]) +class DynamicRegistrationUiDefinitionController { + + @Autowired + JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry + + JsonSchemaResourceLocation jsonSchemaLocation + + @Autowired + ObjectMapper jacksonObjectMapper + + @Autowired + JsonSchemaBuilderService jsonSchemaBuilderService + + @GetMapping + ResponseEntity getUiDefinitionJsonSchema() { + try { + def parsedJson = jacksonObjectMapper.readValue(this.jsonSchemaLocation.url, Map) + return ResponseEntity.ok(parsedJson) + } catch (Exception e) { + log.error(e.getMessage(), e) + return ResponseEntity.status(INTERNAL_SERVER_ERROR).body([jsonParseError : e.getMessage(), sourceUiSchemaDefinitionFile: this.jsonSchemaLocation.url]) + } + } + + @PostConstruct + void init() { + this.jsonSchemaLocation = dynamicRegistrationSchema(this.jsonSchemaResourceLocationRegistry) + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java index 607615adc..8a3ef5aba 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/CoreShibUiConfiguration.java @@ -12,6 +12,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener; import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator; import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissionDelegate; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository; @@ -21,31 +22,37 @@ import edu.internet2.tier.shibboleth.admin.ui.service.DefaultMetadataResolversPositionOrderContainerService; import edu.internet2.tier.shibboleth.admin.ui.service.DirectoryService; import edu.internet2.tier.shibboleth.admin.ui.service.DirectoryServiceImpl; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.EntityService; import edu.internet2.tier.shibboleth.admin.ui.service.FileCheckingFileWritingService; import edu.internet2.tier.shibboleth.admin.ui.service.FileWritingService; import edu.internet2.tier.shibboleth.admin.ui.service.FilterTargetService; +import edu.internet2.tier.shibboleth.admin.ui.service.JPADynamicRegistrationServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.JPAFilterTargetServiceImpl; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolversPositionOrderContainerService; +import edu.internet2.tier.shibboleth.admin.ui.service.ShibRestTemplateDelegate; import edu.internet2.tier.shibboleth.admin.util.AttributeUtility; import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils; import edu.internet2.tier.shibboleth.admin.util.LuceneUtility; import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions; import org.apache.lucene.analysis.Analyzer; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.core.io.Resource; +import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; @@ -54,6 +61,7 @@ import org.springframework.web.util.UrlPathHelper; import javax.servlet.http.HttpServletRequest; +import java.net.URL; @Configuration @Import(SearchConfiguration.class) @@ -234,8 +242,16 @@ public UserUpdatedEntityListener userUpdatedEntityListener(OwnershipRepository r } @Bean - public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepository entityDescriptorRepository, UserService userService) { + public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepository entityDescriptorRepository, UserService userService, DynamicRegistrationInfoRepository driRepo) { // TODO: @jj define type to return for Grouper integration - return new ShibUiPermissionDelegate(entityDescriptorRepository, userService); + return new ShibUiPermissionDelegate(driRepo, entityDescriptorRepository, userService); + } + + @Bean + public DynamicRegistrationService dynamicRegistrationService(DynamicRegistrationInfoRepository driRepo, OwnershipRepository ownershipRepo, + IShibUiPermissionEvaluator permissionEvaluator, UserService userService, IGroupService groupService, + @Qualifier("shibUIConfiguration") ShibUIConfiguration config, RestTemplateBuilder restTemplateBuilder) { + ShibRestTemplateDelegate delegate = new ShibRestTemplateDelegate(config); + return new JPADynamicRegistrationServiceImpl(groupService, driRepo, ownershipRepo, delegate, permissionEvaluator, userService); } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java index fa8f5db18..c310154d7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/JsonSchemaComponentsConfiguration.java @@ -13,6 +13,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.JsonSchemaLocationBuilder; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ALGORITHM_FILTER; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER; +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_REGISTRATION; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ENTITY_ATTRIBUTES_FILTERS; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.EXTERNAL_METADATA_RESOLVER; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.FILESYSTEM_METADATA_RESOLVER; @@ -73,6 +74,11 @@ public class JsonSchemaComponentsConfiguration { @Setter private String algorithmFilterUiSchemaLocation = "classpath:algorithm-filter.schema.json"; + //Configured via @ConfigurationProperties (using setter method) with 'shibui.dynamic-registration-ui-schema-location' property and + // default value set here if that property is not explicitly set in application.properties + @Setter + private String dynamicRegistrationUiSchemaLocation = "classpath:dynamic-registration.schema.json"; + @Bean public JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(ResourceLoader resourceLoader, ObjectMapper jacksonMapper) { return JsonSchemaResourceLocationRegistry.inMemory() @@ -129,6 +135,12 @@ public JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(Res .resourceLoader(resourceLoader) .jacksonMapper(jacksonMapper) .detectMalformedJson(true) + .build()) + .register(DYNAMIC_REGISTRATION, JsonSchemaLocationBuilder.with() + .jsonSchemaLocation(dynamicRegistrationUiSchemaLocation) + .resourceLoader(resourceLoader) + .jacksonMapper(jacksonMapper) + .detectMalformedJson(true) .build()); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java index 50f5a2e75..f1a85a4f9 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/ShibUIConfiguration.java @@ -2,10 +2,35 @@ import lombok.Getter; import lombok.Setter; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.core.io.Resource; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.net.URL; +import java.net.http.HttpClient; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.List; import java.util.Set; @@ -39,4 +64,30 @@ public class ShibUIConfiguration { * A list of roles to bootstrap into the system. */ private Set roles; + + /** + * The URL of the shib idp server ala - https://idp.someschool.edu/idp + */ + private URL shibIdpServer; + + private RestTemplate restTemplate; + + @Profile("very-dangerous") + @Bean + public RestTemplate dangerousRestTemplate() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException { + HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { + public boolean verify(String hostname, SSLSession session) { + return true; + } + }); + + TrustStrategy acceptingTrustStrategy = (x509Certificates, s) -> true; + SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); + SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier()); + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build(); + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); + requestFactory.setHttpClient(httpClient); + restTemplate = new RestTemplate(requestFactory); + return restTemplate; + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java index 49c9e0d90..c3d69d88b 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateController.java @@ -2,17 +2,21 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.exceptions.MetadataFileNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter; +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.exception.InitializationException; import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; +import edu.internet2.tier.shibboleth.admin.ui.exception.UnsupportedShibUiOperationException; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import edu.internet2.tier.shibboleth.admin.ui.service.FilterService; import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tags; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.PatchMapping; @@ -26,6 +30,8 @@ @RequestMapping("/api/activate") @Tags(value = {@Tag(name = "activate")}) public class ActivateController { + @Autowired + private DynamicRegistrationService dynamicRegistrationService; @Autowired private EntityDescriptorService entityDescriptorService; @@ -36,6 +42,21 @@ public class ActivateController { @Autowired private MetadataResolverService metadataResolverService; + @PatchMapping(path = "/DynamicRegistration/{resourceId}/{mode}") + @Transactional + public ResponseEntity enableDynamicRegistration(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException, UnsupportedShibUiOperationException { + if ("enable".equalsIgnoreCase(mode)) { + HttpStatus status = dynamicRegistrationService.enableDynamicRegistration(resourceId); + switch (status) { + case OK: + case CREATED: return ResponseEntity.ok("Service enabled"); + case NOT_FOUND: throw new UnsupportedShibUiOperationException("Request returned NOT FOUND, please contact a system admin to check configuration"); + case FORBIDDEN: throw new ForbiddenException("Request was denied with FORBIDDEN, please contact a system admin to check configuration"); + } + } + throw new UnsupportedShibUiOperationException("Disable is not a valid operation for Dynamic Registrations at this time"); + } + @PatchMapping(path = "/entityDescriptor/{resourceId}/{mode}") @Transactional public ResponseEntity enableEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { @@ -43,7 +64,7 @@ public ResponseEntity enableEntityDescriptor(@PathVariable String resourceId, EntityDescriptorRepresentation edr = entityDescriptorService.updateEntityDescriptorEnabledStatus(resourceId, status); return ResponseEntity.ok(edr); } - + @PatchMapping(path = "/MetadataResolvers/{metadataResolverId}/Filter/{resourceId}/{mode}") @Transactional public ResponseEntity enableFilter(@PathVariable String metadataResolverId, @PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException, ScriptException { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java index 32bf84afb..0798f12d8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApprovalController.java @@ -1,8 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tags; @@ -18,9 +20,20 @@ @RequestMapping("/api/approve") @Tags(value = {@Tag(name = "approve")}) public class ApprovalController { + @Autowired + private DynamicRegistrationService dynamicRegistrationService; + @Autowired private EntityDescriptorService entityDescriptorService; + @PatchMapping(path = "/DynamicRegistration/{resourceId}/{mode}") + @Transactional + public ResponseEntity approveDynamicRegistration(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { + boolean status = "approve".equalsIgnoreCase(mode); + DynamicRegistrationRepresentation drr = dynamicRegistrationService.approveDynamicRegistration(resourceId, status); + return ResponseEntity.ok(drr); + } + @PatchMapping(path = "/entityDescriptor/{resourceId}/{mode}") @Transactional public ResponseEntity approveEntityDescriptor(@PathVariable String resourceId, @PathVariable String mode) throws PersistentEntityNotFound, ForbiddenException { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java index 0c8efc28f..48436eba5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveAndActivateExceptionHandler.java @@ -4,6 +4,7 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.exception.InitializationException; import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; +import edu.internet2.tier.shibboleth.admin.ui.exception.UnsupportedShibUiOperationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -42,6 +43,9 @@ public ResponseEntity handleMetadataFileNotFoundException(MetadataFileNotFoun public ResponseEntity handleScriptException(ScriptException e, WebRequest request) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorResponse(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), e.getMessage())); } - - + + @ExceptionHandler({ UnsupportedShibUiOperationException.class }) + public ResponseEntity handleUnsupportedShibUiOperationException(UnsupportedShibUiOperationException e, WebRequest request) { + return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).body(new ErrorResponse(String.valueOf(HttpStatus.NOT_IMPLEMENTED.value()), e.getMessage())); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationController.java new file mode 100644 index 000000000..c0b314b05 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationController.java @@ -0,0 +1,98 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.exception.MissingRequiredFieldsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.tags.Tags; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import java.net.URI; +import java.util.ConcurrentModificationException; + +@RestController +@RequestMapping("/api") +@Tags(value = {@Tag(name = "oidc")}) +public class DynamicRegistrationController { + private static URI getResourceUriFor(String resourceId) { + return ServletUriComponentsBuilder + .fromCurrentServletMapping().path("/api/DynamicRegistration") + .pathSegment(resourceId) + .build() + .toUri(); + } + + @Autowired + DynamicRegistrationService dynamicRegistrationService; + + @PostMapping("/DynamicRegistration") + @Transactional + public ResponseEntity create(@RequestBody DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException, MissingRequiredFieldsException { + DynamicRegistrationRepresentation persisted = dynamicRegistrationService.createNew(dynRegRepresentation); + return ResponseEntity.created(getResourceUriFor(persisted.getResourceId())).body(persisted); + } + + @GetMapping(value = "/DynamicRegistrations", produces = "application/json") + @Transactional(readOnly = true) + public ResponseEntity getAll() throws ForbiddenException { + return ResponseEntity.ok(dynamicRegistrationService.getAllDynamicRegistrationsBasedOnUserAccess()); + } + + @GetMapping("/DynamicRegistrations/needsApproval") + @Transactional + public ResponseEntity getAllNeedingApproval() throws ForbiddenException { + return ResponseEntity.ok(dynamicRegistrationService.getAllDynamicRegistrationsNeedingApprovalBasedOnUserAccess()); + } + + /** + * @throws ForbiddenException This call is used for the admin needs action list, therefore the user must be an admin + */ + @Transactional + @GetMapping(value = "/DynamicRegistrations/disabledSources") + public ResponseEntity getDisabledMetadataSources() throws ForbiddenException { + return ResponseEntity.ok(dynamicRegistrationService.getDisabledDynamicRegistrations()); + } + + @GetMapping(value = "/DynamicRegistration/{resourceId}", produces = "application/json") + @Transactional(readOnly = true) + public ResponseEntity getOne(@PathVariable String resourceId) throws ForbiddenException { + return ResponseEntity.ok(dynamicRegistrationService.getOne(resourceId)); + } + + @DeleteMapping(value = "/DynamicRegistration/{resourceId}") + @Transactional + public ResponseEntity deleteOne(@PathVariable String resourceId) throws ForbiddenException, PersistentEntityNotFound { + dynamicRegistrationService.delete(resourceId); + return ResponseEntity.noContent().build(); + } + + @PutMapping("/DynamicRegistration/{resourceId}") + @Transactional + public ResponseEntity update(@RequestBody DynamicRegistrationRepresentation dynRegRepresentation, @PathVariable String resourceId) throws ForbiddenException, ConcurrentModificationException, PersistentEntityNotFound { + dynRegRepresentation.setResourceId(resourceId); // This should be the same already, but just to be safe... + DynamicRegistrationRepresentation result = dynamicRegistrationService.update(dynRegRepresentation); + return ResponseEntity.ok().body(result); + } + + @PutMapping("/DynamicRegistration/{resourceId}/changeGroup/{groupId}") + @Transactional + public ResponseEntity updateGroupForEntityDescriptor(@PathVariable String resourceId, @PathVariable String groupId) throws ForbiddenException, PersistentEntityNotFound { + DynamicRegistrationRepresentation result = dynamicRegistrationService.updateGroupForDynamicRegistration(resourceId, groupId); + return ResponseEntity.ok().body(result); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index e8498a6c8..f88ae5d90 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -159,8 +159,7 @@ public ResponseEntity update(@RequestBody EntityDescriptorRepresentation edRe @PutMapping("/EntityDescriptor/{resourceId}/changeGroup/{groupId}") @Transactional - public ResponseEntity updateGroupForEntityDescriptor(@PathVariable String resourceId, @PathVariable String groupId) - throws ForbiddenException, ConcurrentModificationException, PersistentEntityNotFound, InvalidPatternMatchException { + public ResponseEntity updateGroupForEntityDescriptor(@PathVariable String resourceId, @PathVariable String groupId) throws ConcurrentModificationException { EntityDescriptorRepresentation result = entityDescriptorService.updateGroupForEntityDescriptor(resourceId, groupId); return ResponseEntity.ok().body(result); } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerExceptionHandler.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/PersistentEntityControllerExceptionHandler.java similarity index 80% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerExceptionHandler.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/PersistentEntityControllerExceptionHandler.java index e6f46b5fe..7c6fc0182 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerExceptionHandler.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/PersistentEntityControllerExceptionHandler.java @@ -2,6 +2,7 @@ import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.exception.InvalidPatternMatchException; +import edu.internet2.tier.shibboleth.admin.ui.exception.MissingRequiredFieldsException; import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; import org.springframework.http.HttpHeaders; @@ -14,8 +15,8 @@ import java.util.ConcurrentModificationException; -@ControllerAdvice(assignableTypes = {EntityDescriptorController.class}) -public class EntityDescriptorControllerExceptionHandler extends ResponseEntityExceptionHandler { +@ControllerAdvice(assignableTypes = {EntityDescriptorController.class, DynamicRegistrationController.class}) +public class PersistentEntityControllerExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler({ ConcurrentModificationException.class }) public ResponseEntity handleConcurrentModificationException(ConcurrentModificationException e, WebRequest request) { @@ -43,7 +44,12 @@ public ResponseEntity handleObjectIdExistsException(ObjectIdExistsException e headers.setLocation(EntityDescriptorController.getResourceUriFor(e.getMessage())); return ResponseEntity.status(HttpStatus.CONFLICT).headers(headers).body(new ErrorResponse( String.valueOf(HttpStatus.CONFLICT.value()), - String.format("The entity descriptor with entity id [%s] already exists.", e.getMessage()))); + String.format("The persistent entity with id [%s] already exists.", e.getMessage()))); } + + @ExceptionHandler({ MissingRequiredFieldsException.class }) + public ResponseEntity handleMissingRequiredFieldsException(MissingRequiredFieldsException e, WebRequest request) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse(HttpStatus.BAD_REQUEST, e.getMessage())); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ActivatableType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ActivatableType.java index 8042d56d4..e3a9a7387 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ActivatableType.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/ActivatableType.java @@ -1,5 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; public enum ActivatableType { - ENTITY_DESCRIPTOR, METADATA_RESOLVER, FILTER + ENTITY_DESCRIPTOR, METADATA_RESOLVER, FILTER, DYNAMIC_REGISTRATION } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IActivatable.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IActivatable.java index 84a31078a..2c8662a27 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IActivatable.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IActivatable.java @@ -1,7 +1,15 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; + public interface IActivatable { ActivatableType getActivatableType(); void setEnabled(Boolean enabled); + + @JsonIgnore + default String getIdOfOwner() { + return Group.ADMIN_GROUP.getName(); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java index f2e163b0b..541d86fa5 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/IApprovable.java @@ -2,4 +2,6 @@ public interface IApprovable { String getIdOfOwner(); + + void removeLastApproval(); } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/DynamicRegistrationRepresentation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/DynamicRegistrationRepresentation.java new file mode 100644 index 000000000..1b4f1dfc8 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/frontend/DynamicRegistrationRepresentation.java @@ -0,0 +1,119 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.frontend; + +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.GrantType; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@NoArgsConstructor +@Getter +@Setter +public class DynamicRegistrationRepresentation { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"); + + private String applicationType; + private boolean approved; + private String contacts; + private LocalDateTime createdDate; + private String createdBy; + private boolean enabled; + private GrantType grantType; + private String idOfOwner; + private String jwks; + private String logoUri; + private String name; + private LocalDateTime modifiedDate; + private String policyUri; + private String redirectUris; + private String resourceId; + private String responseTypes; + private String scope; + private String subjectType; + private String tokenEndpointAuthMethod; + private String tosUri; + private int version; + private String clientId; + + public DynamicRegistrationRepresentation(DynamicRegistrationInfo dri) { + applicationType = dri.getApplicationType(); + approved = dri.isApproved(); + contacts = dri.getContacts(); + createdBy = dri.getCreatedBy(); + createdDate = dri.getCreatedDate(); + enabled = dri.isEnabled(); + grantType = dri.getGrantType(); + idOfOwner = dri.getIdOfOwner(); + jwks = dri.getJwks(); + logoUri = dri.getLogoUri(); + name = dri.getName(); + modifiedDate = dri.getModifiedDate(); + policyUri = dri.getPolicyUri(); + redirectUris = dri.getRedirectUris(); + resourceId = dri.getResourceId(); + responseTypes = dri.getResponseTypes(); + scope = dri.getScope(); + subjectType = dri.getSubjectType(); + tokenEndpointAuthMethod = dri.getTokenEndpointAuthMethod(); + tosUri = dri.getTosUri(); + version = dri.hashCode(); + clientId = dri.getClientId(); + } + + public DynamicRegistrationInfo buildDynamicRegistrationInfo() { + // Approved and enabled shouldn't be handled from here, and owner and created by shouldn't come from the UI, so we ignore all those + + DynamicRegistrationInfo dri = new DynamicRegistrationInfo(); + dri.setApplicationType(applicationType); +// dri.setApproved(approved); + dri.setContacts(contacts); +// dri.setEnabled(enabled); + dri.setGrantType(grantType); +// dri.setIdOfOwner(idOfOwner); + dri.setJwks(jwks); + dri.setLogoUri(logoUri); + dri.setName(name); + dri.setPolicyUri(policyUri); + dri.setRedirectUris(redirectUris); + dri.setResourceId(resourceId); + dri.setResponseTypes(responseTypes); + dri.setScope(scope); + dri.setSubjectType(subjectType); + dri.setTokenEndpointAuthMethod(tokenEndpointAuthMethod); + dri.setTosUri(tosUri); + return dri; + } + + public String getCreatedDate() { + return createdDate != null ? DATE_TIME_FORMATTER.format(createdDate) : null; + } + + public String getModifiedDate() { + return modifiedDate != null ? DATE_TIME_FORMATTER.format(modifiedDate) : null; + } + + /** + * Do not update approved or change the group here + */ + public DynamicRegistrationInfo updateExistingWithRepValues(DynamicRegistrationInfo dri) { + dri.setApplicationType(applicationType); + dri.setContacts(contacts); + dri.setEnabled(enabled); + dri.setGrantType(grantType); + dri.setJwks(jwks); + dri.setLogoUri(logoUri); + dri.setName(name); + dri.setPolicyUri(policyUri); + dri.setRedirectUris(redirectUris); + dri.setResourceId(resourceId); + dri.setResponseTypes(responseTypes); + dri.setScope(scope); + dri.setSubjectType(subjectType); + dri.setTokenEndpointAuthMethod(tokenEndpointAuthMethod); + dri.setTosUri(tosUri); + return dri; + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java new file mode 100644 index 000000000..77ad95d29 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/DynamicRegistrationInfo.java @@ -0,0 +1,93 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.oidc; + +import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable; +import edu.internet2.tier.shibboleth.admin.ui.domain.ActivatableType; +import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable; +import edu.internet2.tier.shibboleth.admin.ui.domain.IApprovable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; +import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnableType; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.envers.Audited; + +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Lob; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Entity +@Data +@Audited +public class DynamicRegistrationInfo extends AbstractAuditable implements Ownable, IActivatable, IApprovable { + private String applicationType; + private boolean approved; + private String contacts; + private boolean enabled; + private GrantType grantType; + private String idOfOwner; + @Lob + @Type(type = "org.hibernate.type.TextType") + private String jwks; + private String logoUri; + private String name; + private String policyUri; + private String redirectUris; + private String resourceId; + private String responseTypes; + private String scope; + private String subjectType; + private String tokenEndpointAuthMethod; + private String tosUri; + private String clientId; + + @ElementCollection(fetch = FetchType.EAGER) + @EqualsAndHashCode.Exclude + private List approvedBy = new ArrayList<>(); + + @Override + public ActivatableType getActivatableType() { + return ActivatableType.DYNAMIC_REGISTRATION; + } + + @Override + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + @Override + public String getObjectId() { + return getResourceId(); + } + + public String getResourceId() { + if (resourceId == null) { + resourceId = UUID.randomUUID().toString(); + } + return resourceId; + } + + @Override + public OwnableType getOwnableType() { + return OwnableType.DYNAMIC_REGISTRATION; + } + + @Override + public void removeLastApproval() { + if (!approvedBy.isEmpty()) { + approvedBy.remove(approvedBy.size() - 1); + } + } + + public int approvedCount() { + return approvedBy.size(); + } + + public void addApproval(Group currentUserGroup) { + approvedBy.add(currentUserGroup.getName()); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/GrantType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/GrantType.java new file mode 100644 index 000000000..65e28c7fe --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/oidc/GrantType.java @@ -0,0 +1,5 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain.oidc; + +public enum GrantType { + authorization_code, implicit, refresh_token +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/MissingRequiredFieldsException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/MissingRequiredFieldsException.java new file mode 100644 index 000000000..6e046cefa --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/MissingRequiredFieldsException.java @@ -0,0 +1,7 @@ +package edu.internet2.tier.shibboleth.admin.ui.exception; + +public class MissingRequiredFieldsException extends Exception { + public MissingRequiredFieldsException(String entityId) { + super(entityId); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/UnsupportedShibUiOperationException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/UnsupportedShibUiOperationException.java new file mode 100644 index 000000000..e90508131 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/exception/UnsupportedShibUiOperationException.java @@ -0,0 +1,11 @@ +package edu.internet2.tier.shibboleth.admin.ui.exception; + +public class UnsupportedShibUiOperationException extends Exception { + public UnsupportedShibUiOperationException() { + super("Operation unsupport in ShibUI at this time"); + } + + public UnsupportedShibUiOperationException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java index a5e5406ef..dc28fc1ad 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaLocationLookup.java @@ -2,6 +2,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ALGORITHM_FILTER; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER; +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_REGISTRATION; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ENTITY_ATTRIBUTES_FILTERS; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.EXTERNAL_METADATA_RESOLVER; import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.FILESYSTEM_METADATA_RESOLVER; @@ -132,4 +133,15 @@ public static JsonSchemaResourceLocation algorithmFilterSchema(JsonSchemaResourc return resourceLocationRegistry.lookup(ALGORITHM_FILTER) .orElseThrow(() -> new IllegalStateException("JSON schema resource location for algorithm filter is not registered.")); } + + /** + * Searches algorithm filter JSON schema resource location object in the given location registry. + * + * @param resourceLocationRegistry + * @return algorithm filter JSON schema resource location object + * @throws IllegalStateException if schema is not found in the given registry + */ + public static JsonSchemaResourceLocation dynamicRegistrationSchema(JsonSchemaResourceLocationRegistry resourceLocationRegistry) { + return resourceLocationRegistry.lookup(DYNAMIC_REGISTRATION).orElseThrow(() -> new IllegalStateException("JSON schema resource location for dynamic registration is not registered.")); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java index 58b1e2d66..dd3262888 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaResourceLocation.java @@ -96,6 +96,7 @@ public enum SchemaType { // common types METADATA_SOURCES_SAML("MetadataSourcesSAML"), METADATA_SOURCES_OIDC("MetadataSourcesOIDC"), + DYNAMIC_REGISTRATION("DynamicRegistration"), // filter types ENTITY_ATTRIBUTES_FILTERS("EntityAttributesFilters"), diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java index 66fff37ff..2b445fc62 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorProjection.java @@ -2,7 +2,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptorProtocol; import lombok.Getter; -import org.hibernate.criterion.Projection; import java.time.LocalDateTime; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java index 1cfc5ca0b..7fb999568 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java @@ -42,8 +42,7 @@ public interface EntityDescriptorRepository extends JpaRepository getEntityDescriptorsNeedingEnabling(); /** diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupController.java index bb283b140..2b4b2e9c8 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupController.java @@ -6,10 +6,10 @@ import edu.internet2.tier.shibboleth.admin.ui.security.exception.InvalidGroupRegexException; import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService; import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tags; -import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -34,6 +34,9 @@ public class GroupController { @Autowired private EntityDescriptorService entityDescriptorService; + @Autowired + private DynamicRegistrationService dynamicRegistrationService; + @Secured("ROLE_ADMIN") @PostMapping @Transactional @@ -72,6 +75,7 @@ public ResponseEntity getOne(@PathVariable String resourceId) throws Persiste public ResponseEntity update(@RequestBody Group group) throws PersistentEntityNotFound, InvalidGroupRegexException { Group result = groupService.updateGroup(group); entityDescriptorService.checkApprovalStatusOfEntitiesForGroup(result); + dynamicRegistrationService.checkApprovalStatusOfEntitiesForGroup(result); return ResponseEntity.ok(result); } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnableType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnableType.java index 0cf82714c..ed1fdb8bf 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnableType.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/OwnableType.java @@ -1,5 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.security.model; public enum OwnableType { - USER, ENTITY_DESCRIPTOR, METADATA_PROVIDER -} + USER, ENTITY_DESCRIPTOR, METADATA_PROVIDER, DYNAMIC_REGISTRATION +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java index 9351fac71..53eddc63e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/IShibUiPermissionEvaluator.java @@ -2,7 +2,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.Auditable; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; -import liquibase.pro.packaged.T; import org.apache.commons.lang.NotImplementedException; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.core.Authentication; @@ -26,4 +25,4 @@ public interface IShibUiPermissionEvaluator extends PermissionEvaluator { default Collection getAuditableEntities(Authentication authentication, Class auditableType, PermissionType permissionType) throws ForbiddenException {throw new NotImplementedException();} -} +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java index 5db069c5c..0cd22d7a1 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissibleType.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission; public enum ShibUiPermissibleType { - entityDescriptorProjection // represents EntityDescriptorProjections + entityDescriptorProjection, // represents EntityDescriptorProjections + dynamicRegistrationInfo } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java index 0560b569b..2c27126ed 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegate.java @@ -3,11 +3,13 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable; import edu.internet2.tier.shibboleth.admin.ui.domain.IApprovable; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownable; import edu.internet2.tier.shibboleth.admin.ui.security.model.User; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserAccess; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import lombok.AllArgsConstructor; @@ -27,6 +29,8 @@ */ @AllArgsConstructor public class ShibUiPermissionDelegate implements IShibUiPermissionEvaluator { + private DynamicRegistrationInfoRepository dynamicRegistrationInfoRepository; + private EntityDescriptorRepository entityDescriptorRepository; private UserService userService; @@ -46,14 +50,40 @@ public Collection getPersistentEntities(Authentication ignored, ShibUiPermissibl return entityDescriptorRepository.getEntityDescriptorsNeedingEnabling(); case fetch: if (!hasPermission(ignored, null, PermissionType.fetch)) { - throw new ForbiddenException("User has no access rights to get a list of Metadata Sources"); + throw new ForbiddenException("User has no access rights to get a list of : " + shibUiType); } return getAllEntityDescriptorProjectionsBasedOnUserAccess(); } + case dynamicRegistrationInfo: + switch (permissionType) { + case approve: + return getAllDynamicRegistrationInfoObjectsNeedingApprovalBasedOnUserAccess(); + case enable: + // This particular list is used for an admin function, so the user must be an ADMIN + if (!hasPermission(ignored, null, PermissionType.admin)) { + throw new ForbiddenException(); + } + return dynamicRegistrationInfoRepository.getDynamicRegistrationsNeedingEnabling(); + case fetch: + return getAllDynamicRegistrationInfoObjectsBasedOnUserAccess(); + } } return null; } + private List getAllDynamicRegistrationInfoObjectsNeedingApprovalBasedOnUserAccess() { + List groupsToApprove = userService.getGroupsCurrentUserCanApprove(); + return dynamicRegistrationInfoRepository.getAllNeedingApproval(groupsToApprove); + } + + private List getAllDynamicRegistrationInfoObjectsBasedOnUserAccess() { + if (userService.currentUserIsAdmin()) { + return dynamicRegistrationInfoRepository.findAll(); + } else { + return dynamicRegistrationInfoRepository.findAllByIdOfOwner(userService.getCurrentUser().getGroup().getOwnerId()); + } + } + private List getAllEntityDescriptorProjectionsBasedOnUserAccess() { if (userService.currentUserIsAdmin()) { return entityDescriptorRepository.findAllReturnProjections(); @@ -78,7 +108,7 @@ public boolean hasPermission(Authentication ignored, Object targetDomainObject, return targetDomainObject instanceof IApprovable ? userService.getGroupsCurrentUserCanApprove().contains(((IApprovable)targetDomainObject).getIdOfOwner()) : false; case enable: return targetDomainObject instanceof IActivatable ? currentUserCanEnable((IActivatable) targetDomainObject) : false; - case fetch: + case fetch: // we don't care about one object, just the user's ability to fetch data return userService.currentUserIsAdmin() || userService.getCurrentUserAccess().equals(UserAccess.GROUP); case viewOrEdit: return userService.canViewOrEditTarget((Ownable) targetDomainObject); @@ -94,8 +124,9 @@ public boolean hasPermission(Authentication authentication, Serializable targetI private boolean currentUserCanEnable(IActivatable activatableObject) { if (userService.currentUserIsAdmin()) { return true; } switch (activatableObject.getActivatableType()) { + case DYNAMIC_REGISTRATION: case ENTITY_DESCRIPTOR: { - return currentUserHasExpectedRole(Arrays.asList("ROLE_ENABLE" )) && userService.getCurrentUserGroup().getOwnerId().equals(((EntityDescriptor) activatableObject).getIdOfOwner()); + return currentUserHasExpectedRole(Arrays.asList("ROLE_ENABLE" )) && userService.getCurrentUserGroup().getOwnerId().equals(activatableObject.getIdOfOwner()); } // Currently filters and providers dont have ownership, so we just look for the right role case FILTER: diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/DynamicRegistrationInfoRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/DynamicRegistrationInfoRepository.java new file mode 100644 index 000000000..8caa871d1 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/DynamicRegistrationInfoRepository.java @@ -0,0 +1,33 @@ +package edu.internet2.tier.shibboleth.admin.ui.security.repository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface DynamicRegistrationInfoRepository extends JpaRepository { + List findAllByIdOfOwner(String idOfOwner); + + @Query(value="SELECT dri.resourceId FROM DynamicRegistrationInfo dri WHERE dri.idOfOwner = :groupId AND dri.enabled = false") + List findAllResourceIdsByIdOfOwnerAndNotEnabled(@Param("groupId") String groupId); + + DynamicRegistrationInfo findByResourceId(String id); + + @Query(value = "SELECT dri FROM DynamicRegistrationInfo dri " + + " WHERE dri.idOfOwner IN (:groupIds)" + + " AND dri.enabled = false" + + " AND dri.approved = false") + List getAllNeedingApproval(@Param("groupIds") List groupIds); + + @Query(value = "SELECT dri FROM DynamicRegistrationInfo dri WHERE dri.enabled = false") + List getDynamicRegistrationsNeedingEnabling(); + + @Query(value = "SELECT dri FROM DynamicRegistrationInfo dri " + + " WHERE dri.idOfOwner = :groupId" + + " AND dri.enabled = false" + + " AND dri.approved = true") + List getDynamicRegistrationsNeedingEnabling(@Param("groupId") String groupId); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java index 097f745fb..429dfa6c2 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/service/UserService.java @@ -1,7 +1,5 @@ package edu.internet2.tier.shibboleth.admin.ui.security.service; -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; -import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable; import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; import edu.internet2.tier.shibboleth.admin.ui.security.exception.GroupExistsConflictException; import edu.internet2.tier.shibboleth.admin.ui.security.exception.InvalidGroupRegexException; @@ -24,7 +22,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -235,4 +232,8 @@ public void updateUserRole(User user) { throw new RuntimeException(String.format("User with username [%s] has no role defined and therefore cannot be updated!", user.getUsername())); } } + + public boolean currentUserCanEnable() { + return getCurrentUser().getRole().equals("ROLE_ENABLE"); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java new file mode 100644 index 000000000..e9df64a85 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/DynamicRegistrationService.java @@ -0,0 +1,39 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.exception.MissingRequiredFieldsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; +import edu.internet2.tier.shibboleth.admin.ui.exception.UnsupportedShibUiOperationException; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import org.springframework.http.HttpStatus; + +import java.util.List; + +public interface DynamicRegistrationService { + DynamicRegistrationRepresentation approveDynamicRegistration(String resourceId, boolean status) + throws PersistentEntityNotFound, ForbiddenException; + + void checkApprovalStatusOfEntitiesForGroup(Group result); + + DynamicRegistrationRepresentation createNew(DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException, + MissingRequiredFieldsException; + + void delete(String resourceId) throws ForbiddenException, PersistentEntityNotFound; + + HttpStatus enableDynamicRegistration(String resourceId) + throws PersistentEntityNotFound, ForbiddenException, UnsupportedShibUiOperationException; + + List getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException; + + List getAllDynamicRegistrationsNeedingApprovalBasedOnUserAccess() throws ForbiddenException; + + List getDisabledDynamicRegistrations() throws ForbiddenException; + + DynamicRegistrationRepresentation getOne(String resourceId) throws ForbiddenException; + + DynamicRegistrationRepresentation update(DynamicRegistrationRepresentation dynRegRepresentation) throws PersistentEntityNotFound, ForbiddenException; + + DynamicRegistrationRepresentation updateGroupForDynamicRegistration(String resourceId, String groupId) throws ForbiddenException, PersistentEntityNotFound; +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java new file mode 100644 index 000000000..d0b7d2e47 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImpl.java @@ -0,0 +1,255 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException; +import edu.internet2.tier.shibboleth.admin.ui.exception.MissingRequiredFieldsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException; +import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound; +import edu.internet2.tier.shibboleth.admin.ui.exception.UnsupportedShibUiOperationException; +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Owner; +import edu.internet2.tier.shibboleth.admin.ui.security.model.OwnerType; +import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.PermissionType; +import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissibleType; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository; +import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; +import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.List; + +@Slf4j +@Service +@AllArgsConstructor +@NoArgsConstructor +public class JPADynamicRegistrationServiceImpl implements DynamicRegistrationService { + @Autowired + private IGroupService groupService; + + @Autowired + private DynamicRegistrationInfoRepository repository; + + @Autowired + private OwnershipRepository ownershipRepository; + + private ShibRestTemplateDelegate shibRestTemplateDelegate; + + @Autowired + private IShibUiPermissionEvaluator shibUiAuthorizationDelegate; + + @Autowired + private UserService userService; + + @Override + public DynamicRegistrationRepresentation approveDynamicRegistration(String resourceId, boolean status) throws PersistentEntityNotFound, ForbiddenException { + DynamicRegistrationInfo dri = repository.findByResourceId(resourceId); + if (dri == null) { + throw new PersistentEntityNotFound("Dynamic Registration with resourceid[ " + resourceId + " ] was not found for approval"); + } + return changeApproveStatusOfDynamicRepresentation(dri, status); + } + + private DynamicRegistrationRepresentation changeApproveStatusOfDynamicRepresentation(DynamicRegistrationInfo dri, boolean status) throws ForbiddenException { + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), dri, PermissionType.approve)) { + throw new ForbiddenException("You do not have the permissions necessary to approve this dynamic registration."); + } + if (status) { // approve + int approvedCount = dri.approvedCount(); // total number of approvals so far + List theApprovers = groupService.find(dri.getIdOfOwner()).getApproversList(); + if (theApprovers.size() > approvedCount) { // don't add if we already have enough approvals + dri.addApproval(userService.getCurrentUserGroup()); + } + dri.setApproved(dri.approvedCount() >= theApprovers.size()); // future check for multiple approvals needed + dri = repository.save(dri); + } else { // un-approve + dri.removeLastApproval(); + Group ownerGroup = groupService.find(dri.getIdOfOwner()); + dri.setApproved(dri.approvedCount() >= ownerGroup.getApproversList().size()); // safe check in case of weird race conditions from the UI + dri = repository.save(dri); + } + return new DynamicRegistrationRepresentation(dri); + } + + @Override + public DynamicRegistrationRepresentation createNew(DynamicRegistrationRepresentation dynRegRepresentation) throws ObjectIdExistsException, MissingRequiredFieldsException { + if (entityExists(dynRegRepresentation.getResourceId())) { + throw new ObjectIdExistsException(dynRegRepresentation.getResourceId()); + } + + if (StringUtils.isEmpty(dynRegRepresentation.getName()) || StringUtils.isEmpty(dynRegRepresentation.getRedirectUris())) { + throw new MissingRequiredFieldsException("Name and Redirect URIs are both required to create new Dynamic Registration"); + } + + DynamicRegistrationInfo dri = dynRegRepresentation.buildDynamicRegistrationInfo(); + dri.setEnabled(false); // cannot create as enabled + + // "Create new" will use the current user's group as the owner + String ownerId = userService.getCurrentUserGroup().getOwnerId(); + dri.setIdOfOwner(ownerId); + + if (shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin) || + userService.getCurrentUserGroup().getApproversList().isEmpty()) { + dri.setApproved(true); + } + + ownershipRepository.deleteEntriesForOwnedObject(dri); + ownershipRepository.save(new Ownership(userService.getCurrentUserGroup(), dri)); + + return new DynamicRegistrationRepresentation(repository.save(dri)); + } + + private List convertToRepresentations(List temp) { + List result = new ArrayList<>(); + temp.forEach(dri -> result.add(new DynamicRegistrationRepresentation(dri))); + return result; + } + + @Override + public void delete(String resourceId) throws ForbiddenException, PersistentEntityNotFound { + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), null, PermissionType.admin)) { + throw new ForbiddenException("Deleting a Dynamic Registration Source is only allowed by an admin."); + } + DynamicRegistrationInfo dri = repository.findByResourceId(resourceId); + if (dri == null) { + throw new PersistentEntityNotFound("Dynamic Registration not found for resource id: " + resourceId); + } + if (dri.isEnabled()) { + throw new ForbiddenException("Deleting an enabled Dynamic Registration Source is not allowed."); + } + ownershipRepository.deleteEntriesForOwnedObject(dri); + repository.delete(dri); + } + + @Override + public HttpStatus enableDynamicRegistration(String resourceId) throws PersistentEntityNotFound, ForbiddenException, UnsupportedShibUiOperationException { + DynamicRegistrationInfo existingDri = repository.findByResourceId(resourceId); + if (existingDri == null) { + throw new PersistentEntityNotFound(String.format("The dynamic registration with id [%s] was not found for update.", existingDri.getResourceId())); + } + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.enable)) { + throw new ForbiddenException("You do not have the permissions necessary to enable this service"); + } + HttpStatus status = shibRestTemplateDelegate.sendRequest(existingDri); + if (status == HttpStatus.CREATED || status == HttpStatus.OK) { + existingDri.setEnabled(true); + existingDri.setApproved(true); + repository.save(existingDri); + } + return status; + } + + private boolean entityExists(String id) { + return repository.findByResourceId(id) != null ; + } + + @Override + public List getAllDynamicRegistrationsBasedOnUserAccess() throws ForbiddenException { + List temp = (List) shibUiAuthorizationDelegate.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.dynamicRegistrationInfo, PermissionType.fetch); + return convertToRepresentations(temp); + } + + @Override + public List getAllDynamicRegistrationsNeedingApprovalBasedOnUserAccess() throws ForbiddenException { + List temp = (List) shibUiAuthorizationDelegate.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.dynamicRegistrationInfo, PermissionType.approve); + return convertToRepresentations(temp); + } + + @Override + public List getDisabledDynamicRegistrations() throws ForbiddenException { + List temp = (List) shibUiAuthorizationDelegate.getPersistentEntities(userService.getCurrentUserAuthentication(), ShibUiPermissibleType.dynamicRegistrationInfo, PermissionType.enable); + return convertToRepresentations(temp); + } + + @Override + public DynamicRegistrationRepresentation getOne(String resourceId) throws ForbiddenException { + DynamicRegistrationInfo existingDri = repository.findByResourceId(resourceId); + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.viewOrEdit)) { + throw new ForbiddenException(); + } + return new DynamicRegistrationRepresentation(existingDri); + } + + @Override + public DynamicRegistrationRepresentation update(DynamicRegistrationRepresentation dynRegRepresentation) + throws PersistentEntityNotFound, ForbiddenException, ConcurrentModificationException { + DynamicRegistrationInfo existingDri = repository.findByResourceId(dynRegRepresentation.getResourceId()); + if (existingDri == null) { + throw new PersistentEntityNotFound(String.format("The dynamic registration with id [%s] was not found for update.", existingDri.getResourceId())); + } + if (dynRegRepresentation.isEnabled() && !shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.enable)) { + throw new ForbiddenException("You do not have the permissions necessary to enable this service."); + } + if (StringUtils.isEmpty(dynRegRepresentation.getIdOfOwner())) { + dynRegRepresentation.setIdOfOwner(StringUtils.isNotEmpty(existingDri.getIdOfOwner()) ? existingDri.getIdOfOwner() : userService.getCurrentUserGroup().getOwnerId()); + } + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.viewOrEdit)) { + throw new ForbiddenException(); + } + // Verify we're the only one attempting to update the EntityDescriptor + if (dynRegRepresentation.getVersion() != existingDri.hashCode()) { + throw new ConcurrentModificationException(String.format("A concurrent modification has occured on entity descriptor with entity id [%s]. Please refresh and try again", dynRegRepresentation.getResourceId())); + } + existingDri = dynRegRepresentation.updateExistingWithRepValues(existingDri); + + existingDri = repository.save(existingDri); + ownershipRepository.deleteEntriesForOwnedObject(existingDri); + ownershipRepository.save(new Ownership(new Owner() { + public String getOwnerId() { return dynRegRepresentation.getIdOfOwner(); } + public OwnerType getOwnerType() { return OwnerType.GROUP; } + }, existingDri)); + return new DynamicRegistrationRepresentation(existingDri); + } + + @Override + public DynamicRegistrationRepresentation updateGroupForDynamicRegistration(String resourceId, String groupId) throws ForbiddenException, PersistentEntityNotFound { + DynamicRegistrationInfo existingDri = repository.findByResourceId(resourceId); + if (existingDri == null) { + throw new PersistentEntityNotFound(String.format("The dynamic registration with id [%s] was not found for update.", existingDri.getResourceId())); + } + if (!shibUiAuthorizationDelegate.hasPermission(userService.getCurrentUserAuthentication(), existingDri, PermissionType.admin)) { + throw new ForbiddenException("You do not have the permissions necessary to change the group for this service."); + } + existingDri.setIdOfOwner(groupId); + + Group group = groupService.find(groupId); + ownershipRepository.deleteEntriesForOwnedObject(existingDri); + ownershipRepository.save(new Ownership(group, existingDri)); + // check and see if we need to update the approved status + if (!existingDri.isEnabled()) { + int numApprovers = group.getApproversList().size(); + existingDri.setApproved(!(numApprovers > 0 && existingDri.approvedCount() < numApprovers)); + } + + DynamicRegistrationInfo savedEntity = repository.save(existingDri); + return new DynamicRegistrationRepresentation(savedEntity); + } + + /** + * Update the approval status of entities that were in some approval state but the group approvers were added/removed. + */ + @Override + public void checkApprovalStatusOfEntitiesForGroup(Group group) { + repository.findAllResourceIdsByIdOfOwnerAndNotEnabled(group.getResourceId()).forEach(id -> { + DynamicRegistrationInfo dri = repository.findByResourceId(id); + int approvedCount = dri.approvedCount(); // total number of approvals so far + List theApprovers = groupService.find(dri.getIdOfOwner()).getApproversList(); + dri.setApproved(approvedCount >= theApprovers.size()); + dri = repository.save(dri); + }); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibRestTemplateDelegate.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibRestTemplateDelegate.java new file mode 100644 index 000000000..bb8720334 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/ShibRestTemplateDelegate.java @@ -0,0 +1,116 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration; +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo; +import edu.internet2.tier.shibboleth.admin.ui.exception.UnsupportedShibUiOperationException; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Requires that the shib server url be non-null. The URL of the shib idp server ala - https://idp.someschool.edu/idp + * The URL is used to both fetch a token from Shib as well as to call the OIDC plugin + */ +public class ShibRestTemplateDelegate { + ShibUIConfiguration config; + private URL tokenURL; + private URL registrationURL; + private RestTemplate restTemplate; + + public ShibRestTemplateDelegate(ShibUIConfiguration config) { + this.config = config; + URL url = config.getShibIdpServer(); + if (url != null) { + try { + registrationURL = new URL(url.toExternalForm() + "/profile/oidc/register"); + tokenURL = new URL(url.toExternalForm() + "/profile/admin/oidc/issue-registration-access-token?policyId=shibboleth.DefaultRelyingParty"); + } + catch (MalformedURLException e) { + tokenURL = null; + registrationURL = null; + } + } + } + + /** + * Handles sending the Dynamic Registration request to Shibboleth (assuming the URL for Shib was configured for this) + * @throws UnsupportedShibUiOperationException + */ + public HttpStatus sendRequest(DynamicRegistrationInfo dri) throws UnsupportedShibUiOperationException { + if (config.getRestTemplate() != null) { + this.restTemplate = config.getRestTemplate(); + } + + if (tokenURL == null) { + throw new UnsupportedShibUiOperationException("Dynamic Registration endpoint not configured properly, please contact your system admin."); + } + try { + // Fetch an access token from SHIBBOLETH + ResponseEntity tokenResponse = restTemplate.postForEntity(tokenURL.toURI(), "", Map.class); + String token = ((Map)tokenResponse.getBody()).get("access_token").toString(); + + HttpEntity entity = convertDynamicReg(dri, token); + ResponseEntity response = restTemplate.exchange(registrationURL.toURI(), HttpMethod.POST, entity, Map.class); + if (response.getStatusCode() == HttpStatus.CREATED) { + dri.setClientId(((Map)response.getBody()).get("client_id").toString()); + } + return response.getStatusCode(); + } + catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private HttpEntity convertDynamicReg(DynamicRegistrationInfo dri, String token) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("Authorization", "Bearer " + token); + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // skip any null values + + Map valuesMap = new HashMap<>(); + valuesMap.put("redirect_uris", arrayOrNull(dri.getRedirectUris())); + valuesMap.put("response_types", arrayOrNull(dri.getResponseTypes())); + valuesMap.put("grant_types", dri.getGrantType()); + valuesMap.put("application_type", dri.getApplicationType()); + valuesMap.put("contacts", arrayOrNull(dri.getContacts())); + valuesMap.put("subject_type", dri.getSubjectType()); + valuesMap.put("jwks", StringUtils.defaultIfEmpty(dri.getJwks(), null)); + valuesMap.put("token_endpoint_auth_method", dri.getTokenEndpointAuthMethod()); + valuesMap.put("logo_uri", dri.getLogoUri()); + valuesMap.put("policy_uri", dri.getPolicyUri()); + valuesMap.put("tos_uri", dri.getTosUri()); + valuesMap.put("scope", dri.getScope()); + + String json; + try { + json = mapper.writeValueAsString(valuesMap); + } + catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return new HttpEntity(json, headers); + } + + private String[] arrayOrNull(String values) { + if (values == null) { + return null; + } + return values.split(" "); + } +} \ No newline at end of file diff --git a/backend/src/main/resources/dynamic-registration.schema.json b/backend/src/main/resources/dynamic-registration.schema.json new file mode 100644 index 000000000..3d109a4a0 --- /dev/null +++ b/backend/src/main/resources/dynamic-registration.schema.json @@ -0,0 +1,100 @@ +{ + "type": "object", + "required": [ + "redirectUris", + "name" + ], + "properties": { + "name": { + "title": "label.dynamic-registration-name", + "description": "tooltip.dynamic-registration-name", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "redirectUris": { + "title": "label.dynamic-registration-redirectUris", + "description": "tooltip.dynamic-registration-redirectUris", + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "approved": { + "title": "label.approved", + "description": "tooltip.approved", + "type": "boolean" + }, + "enabled": { + "title": "label.enabled", + "description": "tooltip.enabled", + "type": "boolean" + }, + "applicationType": { + "title": "label.dynamic-registration-applicationType", + "description": "tooltip.dynamic-registration-applicationType", + "type": "string" + }, + "contacts": { + "title": "label.dynamic-registration-contacts", + "description": "tooltip.dynamic-registration-contacts", + "type": "string" + }, + "grantType": { + "title": "label.dynamic-registration-grantTypes", + "description": "tooltip.dynamic-registration-grantTypes", + "type": "string", + "minLength": 1, + "enum": [ + "authorization_code", + "implicit", + "refresh_token" + ], + "enumNames": [ + "value.authorization-code", + "value.implicit", + "value.refresh-token" + ] + }, + "jwks": { + "title": "label.dynamic-registration-jwks", + "description": "tooltip.dynamic-registration-jwks", + "type": "string", + "maxLength": 4000 + }, + "logoUri": { + "title": "label.dynamic-registration-logo-uri", + "description": "tooltip.dynamic-registration-logo-uri", + "type": "string" + }, + "policyUri": { + "title": "label.dynamic-registration-policy-uri", + "description": "tooltip.dynamic-registration-policy-uri", + "type": "string" + }, + "responseTypes": { + "title": "label.dynamic-registration-responseTypes", + "description": "tooltip.dynamic-registration-responseTypes", + "type": "string" + }, + "scope": { + "title": "label.dynamic-registration-scope", + "description": "tooltip.dynamic-registration-scope", + "type": "string" + }, + "subjectType": { + "title": "label.dynamic-registration-subjectType", + "description": "tooltip.dynamic-registration-subjectType", + "type": "string" + }, + "tokenEndpointAuthMethod": { + "title": "label.dynamic-registration-tokenEndpointAuthMethod", + "description": "tooltip.dynamic-registration-tokenEndpointAuthMethod", + "type": "string" + }, + "tosUri": { + "title": "label.dynamic-registration-tosuri", + "description": "tooltip.dynamic-registration-tosuri", + "type": "string" + } + } +} \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 08950568c..bdfec4cc5 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -71,6 +71,8 @@ action.enable=Enable action.disable=Disable action.get-latest=Get latest changes action.download=Download +action.metadata-sources=Metadata Sources +action.metadata-providers=Metadata Providers action.add-new-role=Add new role action.roles=Roles @@ -957,7 +959,56 @@ tooltip.IDTokenLifetime.browser=Lifetime of ID token (browser) tooltip.includeIssuerInResponse=Whether to include issuer -parameter in the responses as specified by RFC 9207. If set to true also consider including authorization_response_iss_parameter_supported to the OP metadata. tooltip.refreshTokenLifetime.oauth=Lifetime of refresh token tooltip.alwaysIncludedAttributes.browser=Specifies IdPAttributes to always include in ID token regardless of response_type -tooltip.encryptionOptional=Whether the absence of encryption details in a client's metadata should fail when issuing an ID token +tooltip.encryptionOptional=Whether the absence of encryption details in a client\u0027s metadata should fail when issuing an ID token tooltip.IDTokenLifetime=Lifetime of ID token issued to client tooltip.deniedUserInfoAttributes=Specifies IdPAttributes to omit from UserInfo token -tooltip.resolveAttributes.oauth=Whether to run the attribute resolution/filtering step \ No newline at end of file +tooltip.resolveAttributes.oauth=Whether to run the attribute resolution/filtering step + +label.dynamic-registration=Dynamic Registration +label.dynamic-registrations=Dynamic Registrations +label.dynamic-registration-configuration=Dynamic Registration +action.dynamic-registrations=Dynamic registrations +action.add-new-dynamic-registration=Add a new dynamic registration +label.dynamic-registration-name=Name +label.current-dynamic-registrations=Current dynamic registrations +label.new-dynamic-registration=Add a new dynamic registration +label.edit-dynamic-registration=Edit dynamic registration +message.delete-dynamic-registration-title=Delete dynamic registration? +message.delete-dynamic-registration-body=You are requesting to delete a dynamic registration. If you complete this process the registration will be removed. This cannot be undone. Do you wish to continue? +label.enable-dynamic-registrations=Enable Dynamic Registrations +label.approve-dynamic-registrations=Approve Dynamic Registrations + +label.dynamic-registration-name=Name +tooltip.dynamic-registration-name=Name used to identify the registration on the Shibboleth IDP UI Dashboard. +label.dynamic-registration-redirectUris=Redirect Uris +tooltip.dynamic-registration-redirectUris=Array of Redirection URI values used by the Client. One of these registered Redirection URI values MUST exactly match the redirect_uri parameter value used in each Authorization Request. +label.dynamic-registration-responseTypes=Response Types +tooltip.dynamic-registration-responseTypes=JSON array containing a list of the OAuth 2.0 response_type values that the Client is declaring that it will restrict itself to using. If not present, Shibboleth will use a default of "code" +label.dynamic-registration-grantTypes=Grant Types +tooltip.dynamic-registration-grantTypes=JSON array containing a list of the OAuth 2.0 Grant Types that the Client is declaring that it will restrict itself to using. +label.dynamic-registration-applicationType=Application Type +tooltip.dynamic-registration-applicationType=Kind of the application. Web Clients using the OAuth Implicit Grant Type MUST only register URLs using the https scheme as redirect_uris (they MUST NOT use localhost as the hostname). +label.dynamic-registration-contacts=Contacts +tooltip.dynamic-registration-contacts=Array of e-mail addresses of people responsible for this Client. This might be used by some providers to enable a Web user interface to modify the Client information. +label.dynamic-registration-subjectType=Subject Type +tooltip.dynamic-registration-subjectType=Subject type requested for responses to this Client. The subject_types_supported Discovery parameter contains a list of the supported subject_type values for this server. +label.dynamic-registration-jwks=Jwks +tooltip.dynamic-registration-jwks=Client\u0027s JSON Web Key Set [JWK] document, passed by value. +label.dynamic-registration-jwksUri=Jwks Uri +tooltip.dynamic-registration-jwksUri=URL for the Client\u0027s JSON Web Key Set [JWK] document. If the Client signs requests to the Server, it contains the signing key(s) the Server uses to validate signatures from the Client. +label.dynamic-registration-tokenEndpointAuthMethod=Token Endpoint Auth Method +tooltip.dynamic-registration-tokenEndpointAuthMethod=Requested Client Authentication method for the Token Endpoint. +label.dynamic-registration-logo-uri=Logo Uri +tooltip.dynamic-registration-logo-uri=URL that references a logo for the Client application. If present, the server SHOULD display this image to the End-User during approval. +label.dynamic-registration-policy-uri=Policy Uri +tooltip.dynamic-registration-policy-uri=URL that the Relying Party Client provides to the End-User to read about the how the profile data will be used. The value of this field MUST point to a valid web page. +label.dynamic-registration-tosuri=TOS Uri +tooltip.dynamic-registration-tosuri=URL that the Relying Party Client provides to the End-User to read about the Relying Party\u0027s terms of service. The value of this field MUST point to a valid web page. +label.dynamic-registration-scope=Scope +tooltip.dynamic-registration-scope=If present, all the requested scopes are added to the stored client metadata. If not present, then the default scope set is stored. The default scope (space-separated list of values, e.g. "openid info") can be configured with the idp.oidc.dynreg.defaultScope property. +label.dynamic-registration-enabled=Enabled +tooltip.dynamic-registration-enabled=Represents whether or not this registration has been sent to the Shibboleth IDP. + +value.authorization-code=Authorization Code +value.implicit=Implicit +value.refresh-token=Refresh Token \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy index 791cbbc2e..ab5b88d1c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/AbstractBaseDataJpaTest.groovy @@ -1,6 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui -import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor + import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy index 9c5b88df1..97e826003 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/BaseDataJpaTestConfiguration.groovy @@ -14,6 +14,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.GroupUpdat import edu.internet2.tier.shibboleth.admin.ui.security.model.listener.UserUpdatedEntityListener import edu.internet2.tier.shibboleth.admin.ui.security.permission.IShibUiPermissionEvaluator import edu.internet2.tier.shibboleth.admin.ui.security.permission.ShibUiPermissionDelegate +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting @@ -110,7 +111,7 @@ class BaseDataJpaTestConfiguration { } @Bean - public IShibUiPermissionEvaluator shibUiPermissionEvaluator(EntityDescriptorRepository entityDescriptorRepository, UserService userService) { - return new ShibUiPermissionDelegate(entityDescriptorRepository, userService); + public IShibUiPermissionEvaluator shibUiPermissionEvaluator(DynamicRegistrationInfoRepository driRepo, EntityDescriptorRepository entityDescriptorRepository, UserService userService) { + return new ShibUiPermissionDelegate(driRepo, entityDescriptorRepository, userService); } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy index e443888dc..c41bee896 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/TestConfiguration.groovy @@ -23,6 +23,7 @@ import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.web.client.RestTemplateBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.io.ClassPathResource @@ -55,6 +56,13 @@ class TestConfiguration { this.metadataResolverRepository = metadataResolverRepository } + @Bean + RestTemplateBuilder restTemplateBuilder() { + RestTemplateBuilder result = new RestTemplateBuilder() + return result; + } + + @Bean CustomEntityAttributesDefinitionServiceImpl customEntityAttributesDefinitionServiceImpl() { new CustomEntityAttributesDefinitionServiceImpl().with { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateControllerTests.groovy index 8dfd0cf29..6354f5538 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ActivateControllerTests.groovy @@ -3,6 +3,8 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.GrantType import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository @@ -10,6 +12,8 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService import edu.internet2.tier.shibboleth.admin.ui.service.EntityService import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin @@ -24,6 +28,7 @@ import javax.transaction.Transactional import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + // TODO: This is only checking activation for EntityDescriptors. Expanding for resolvers not included class ActivateControllerTests extends AbstractBaseDataJpaTest { @Subject @@ -32,6 +37,12 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { @Autowired ObjectMapper mapper + @Autowired + DynamicRegistrationInfoRepository dynamicRegistrationInfoRepository + + @Autowired + DynamicRegistrationService dynamicRegistrationService + @Autowired EntityService entityService @@ -51,6 +62,7 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { def setup() { controller = new ActivateController() controller.entityDescriptorService = entDescriptorService + controller.dynamicRegistrationService = dynamicRegistrationService mockMvc = MockMvcBuilders.standaloneSetup(controller).build() EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) @@ -105,6 +117,23 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { entityDescriptor = entityDescriptorRepository.save(entityDescriptor) defaultEntityDescriptorResourceId = entityDescriptor.getResourceId() + + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "AAA", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + dynamicRegistrationInfoRepository.saveAndFlush(dynReg) + } + + @WithMockUser(value = "AUser", roles = ["USER"]) + def 'Owner group cannot activate their own dynamic registration without approvals'() { + expect: + try { + mockMvc.perform(patch("/api/activate/DynamicRegistration/uuid-1/enable")) + } + catch (Exception e) { + e instanceof ForbiddenException + } } @WithMockUser(value = "AUser", roles = ["USER"]) @@ -118,6 +147,17 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { } } + @WithMockUser(value = "DUser", roles = ["USER"]) + def 'non-owner group cannot activate dynamic registration'() { + expect: + try { + mockMvc.perform(patch("/api/activate/DynamicRegistration/uuid-1/enable")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + } + @WithMockUser(value = "DUser", roles = ["USER"]) def 'non-owner group cannot activate entity descriptor'() { expect: @@ -129,6 +169,24 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { } } +// @WithMockAdmin +// def 'Admin can activate an dynamic registration without approval'() { +// given: +// def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-2', enabled: false, applicationType: 'apptype', +// approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', +// redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', +// tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) +// dynamicRegistrationService.createNew(new DynamicRegistrationRepresentation(dynReg)) +// +// when: +// def result = mockMvc.perform(patch("/api/activate/DynamicRegistration/uuid-2/enable")) +// +// then: +// result.andExpect(status().isOk()) +// .andExpect(jsonPath("\$.resourceId").value("uuid-2")) +// .andExpect(jsonPath("\$.enabled").value(true)) +// } + @WithMockAdmin def 'Admin can activate an entity descriptor without approval'() { when: @@ -140,6 +198,26 @@ class ActivateControllerTests extends AbstractBaseDataJpaTest { .andExpect(jsonPath("\$.serviceEnabled").value(true)) } +// @WithMockUser(value = "AUser", roles = ["USER"]) +// def 'Owner group can enable their own dynamic registration with approvals'() { +// when: +// def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-2', enabled: false, idOfOwner: "AAA", applicationType: 'apptype', +// approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', +// redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', +// tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) +// dynReg = dynamicRegistrationService.createNew(dynReg) +// dynReg.addApproval(groupService.find("CCC")) +// dynamicRegistrationService.update(dynReg) +// entityManager.flush() +// +// def result = mockMvc.perform(patch("/api/activate/DynamicRegistration/uuid-2/enable")) +// +// then: +// result.andExpect(status().isOk()) +// .andExpect(jsonPath("\$.resourceId").value('uuid-2')) +// .andExpect(jsonPath("\$.enabled").value(true)) +// } + @WithMockUser(value = "AUser", roles = ["USER"]) def 'Owner group can enable their own entity descriptor with approvals'() { when: diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy index eaf337064..b676ef724 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ApproveControllerTests.groovy @@ -3,6 +3,8 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import com.fasterxml.jackson.databind.ObjectMapper import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.GrantType import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository @@ -10,6 +12,8 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService import edu.internet2.tier.shibboleth.admin.ui.service.EntityService import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils @@ -31,6 +35,12 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { @Autowired ObjectMapper mapper + @Autowired + DynamicRegistrationInfoRepository dynamicRegistrationInfoRepository + + @Autowired + DynamicRegistrationService dynamicRegistrationService + @Autowired EntityService entityService @@ -50,6 +60,7 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { def setup() { controller = new ApprovalController() controller.entityDescriptorService = entDescriptorService + controller.dynamicRegistrationService = dynamicRegistrationService mockMvc = MockMvcBuilders.standaloneSetup(controller).build() EntityDescriptorConversionUtils.setOpenSamlObjects(openSamlObjects) @@ -104,6 +115,25 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { entityDescriptor = entityDescriptorRepository.save(entityDescriptor) defaultEntityDescriptorResourceId = entityDescriptor.getResourceId() + + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "AAA", applicationType: 'apptype', + approved: false, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + dynamicRegistrationInfoRepository.saveAndFlush(dynReg) + } + + @WithMockUser(value = "AUser", roles = ["USER"]) + def 'Owner group cannot approve their own dynamic registration'() { + expect: + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + try { + mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/approve")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false } @WithMockUser(value = "AUser", roles = ["USER"]) @@ -119,6 +149,19 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false } + @WithMockUser(value = "DUser", roles = ["USER"]) + def 'non-approver group cannot approve dynamic registration'() { + expect: + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + try { + mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/approve")) + } + catch (Exception e) { + e instanceof ForbiddenException + } + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + } + @WithMockUser(value = "DUser", roles = ["USER"]) def 'non-approver group cannot approve entity descriptor'() { expect: @@ -132,6 +175,22 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() == false } + @WithMockUser(value = "BUser", roles = ["USER"]) + def 'Approver group can approve an dynamic registration'() { + expect: + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + + when: + def result = mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/approve")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.approved").value(true)) + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() + } + @WithMockUser(value = "BUser", roles = ["USER"]) def 'Approver group can approve an entity descriptor'() { expect: @@ -148,6 +207,32 @@ class ApproveControllerTests extends AbstractBaseDataJpaTest { entityDescriptorRepository.findByResourceId(defaultEntityDescriptorResourceId).isApproved() } + @WithMockUser(value = "BUser", roles = ["USER"]) + def 'Approver can approve and un-approve an dynamic registration'() { + expect: + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + + when: + def result = mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/approve")) + + then: + result.andExpect(status().isOk()) + .andExpect(jsonPath("\$.resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.approved").value(true)) + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() + + when: + def result2 = mockMvc.perform(patch("/api/approve/DynamicRegistration/uuid-1/unapprove")) + + then: + result2.andExpect(status().isOk()) + .andExpect(jsonPath("\$.resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.approved").value(false)) + dynamicRegistrationInfoRepository.findByResourceId("uuid-1").isApproved() == false + } + @WithMockUser(value = "BUser", roles = ["USER"]) def 'Approver can approve and un-approve an entity descriptor'() { expect: diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy index 029bc7a53..45eed2ea1 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -15,6 +15,7 @@ import spock.lang.Specification import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.JsonSchemaLocationBuilder import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ALGORITHM_FILTER import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_HTTP_METADATA_RESOLVER +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.DYNAMIC_REGISTRATION import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.ENTITY_ATTRIBUTES_FILTERS import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.FILESYSTEM_METADATA_RESOLVER import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation.SchemaType.LOCAL_DYNAMIC_METADATA_RESOLVER @@ -100,7 +101,12 @@ class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Speci .jacksonMapper(jacksonMapper) .detectMalformedJson(false) .build()) - + .register(DYNAMIC_REGISTRATION, JsonSchemaLocationBuilder.with() + .jsonSchemaLocation('classpath:dynamic-registration.schema.json') + .resourceLoader(resourceLoader) + .jacksonMapper(jacksonMapper) + .detectMalformedJson(false) + .build()) } } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationControllerTests.groovy new file mode 100644 index 000000000..a1d043a72 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/DynamicRegistrationControllerTests.groovy @@ -0,0 +1,527 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.DynamicRegistrationRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.GrantType +import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException +import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group +import edu.internet2.tier.shibboleth.admin.ui.security.model.Role +import edu.internet2.tier.shibboleth.admin.ui.security.model.User +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService +import edu.internet2.tier.shibboleth.admin.ui.service.EntityService +import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin +import org.hamcrest.Matchers +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.web.servlet.result.MockMvcResultHandlers +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import spock.lang.Subject + +import javax.persistence.EntityManager + +import static org.springframework.http.MediaType.APPLICATION_JSON +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +class DynamicRegistrationControllerTests extends AbstractBaseDataJpaTest { + @Autowired + DynamicRegistrationService dynamicRegistrationService; + + @Autowired + DynamicRegistrationInfoRepository repo + + @Autowired + EntityManager entityManager + + @Autowired + EntityService entityService + + @Autowired + ObjectMapper mapper + + def mockMvc + + @Subject + def controller + + @Transactional + def setup() { + Group ga = new Group() + ga.setResourceId("testingGroupAAA") + ga.setName("Group AAA") + ga = groupService.createGroup(ga) + + Group gb = new Group() + gb.setResourceId("testingGroupBBB") + gb.setName("Group BBB") + gb = groupService.createGroup(gb) + + controller = new DynamicRegistrationController() + controller.dynamicRegistrationService = dynamicRegistrationService + mockMvc = MockMvcBuilders.standaloneSetup(controller).build() + + Optional userRole = roleRepository.findByName("ROLE_USER") + User user = new User(username: "someUser", roles:[userRole.get()], password: "foo") + user.setGroup(gb) + userService.save(user) + } + + @WithMockAdmin + def 'DELETE as admin'() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "admingroup", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg) + def dris = repo.findAll() + + expect: + dris.size() == 1 + + when: + def result = mockMvc.perform(delete("/api/DynamicRegistration/" + dris.get(0).getResourceId())) + + then: + result.andExpect(status().isNoContent()) + repo.findByResourceId("uuid-1") == null + repo.findAll().size() == 0 + + // persistent entity doesn't exist + try { + def result2 = mockMvc.perform(delete("/api/DynamicRegistration/uuid-1")) + } catch (Exception e) { + e instanceof PersistentEntityNotFound + } + } + + + @WithMockUser(value = "someUser", roles = ["USER"]) + def 'DELETE as non-admin'() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "admingroup", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg) + def dris = repo.findAll() + + expect: + dris.size() == 1 + try { + def result = mockMvc.perform(delete("/api/DynamicRegistration/" + dris.get(0).getResourceId())) + } catch (Exception e) { + e instanceof ForbiddenException + } + } + + @WithMockAdmin + def 'GET /DynamicRegistrations with empty repository as admin'() { + when: + def result = mockMvc.perform(get('/api/DynamicRegistrations')) + + then: + result.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)).andExpect(content().json('[]')) + } + + @WithMockAdmin + def 'GET /DynamicRegistrations with 1 record in repository as admin'() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "admingroup", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg) + + when: + def result = mockMvc.perform(get('/api/DynamicRegistrations')) + + then: + result.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.[0].enabled").value(false)) + .andExpect(jsonPath("\$.[0].idOfOwner").value("admingroup")) + .andExpect(jsonPath("\$.[0].applicationType").value("apptype")) + .andExpect(jsonPath("\$.[0].contacts").value("contacts")) + .andExpect(jsonPath("\$.[0].jwks").value("jwks")) + .andExpect(jsonPath("\$.[0].logoUri").value("logouri")) + .andExpect(jsonPath("\$.[0].policyUri").value("policyuri")) + .andExpect(jsonPath("\$.[0].redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.[0].responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.[0].scope").value("scope")) + .andExpect(jsonPath("\$.[0].subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.[0].tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.[0].tosUri").value("tosuri")) + .andExpect(jsonPath("\$.[0].grantType").value("implicit")) + + when: // add another so there should be two results + def dynReg2 = new DynamicRegistrationInfo(resourceId: 'uuid-2', enabled: false, idOfOwner: "admingroup", applicationType: 'apptype2', + approved: true, contacts: 'contacts2', jwks: 'jwks2', logoUri: 'logouri2', policyUri: 'policyuri2', + redirectUris: 'redirecturis2', responseTypes: 'responsetypes2', scope: 'scope2', subjectType: 'subjecttype2', + tokenEndpointAuthMethod: 'token2', tosUri: 'tosuri2', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg2) + def result2 = mockMvc.perform(get('/api/DynamicRegistrations')) + + then: + result2.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.[0].enabled").value(false)) + .andExpect(jsonPath("\$.[0].idOfOwner").value("admingroup")) + .andExpect(jsonPath("\$.[0].applicationType").value("apptype")) + .andExpect(jsonPath("\$.[0].contacts").value("contacts")) + .andExpect(jsonPath("\$.[0].jwks").value("jwks")) + .andExpect(jsonPath("\$.[0].logoUri").value("logouri")) + .andExpect(jsonPath("\$.[0].policyUri").value("policyuri")) + .andExpect(jsonPath("\$.[0].redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.[0].responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.[0].scope").value("scope")) + .andExpect(jsonPath("\$.[0].subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.[0].tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.[0].tosUri").value("tosuri")) + .andExpect(jsonPath("\$.[0].grantType").value("implicit")) + + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[1].resourceId").value("uuid-2")) + .andExpect(jsonPath("\$.[1].enabled").value(false)) + .andExpect(jsonPath("\$.[1].idOfOwner").value("admingroup")) + .andExpect(jsonPath("\$.[1].applicationType").value("apptype2")) + .andExpect(jsonPath("\$.[1].contacts").value("contacts2")) + .andExpect(jsonPath("\$.[1].jwks").value("jwks2")) + .andExpect(jsonPath("\$.[1].logoUri").value("logouri2")) + .andExpect(jsonPath("\$.[1].policyUri").value("policyuri2")) + .andExpect(jsonPath("\$.[1].redirectUris").value("redirecturis2")) + .andExpect(jsonPath("\$.[1].responseTypes").value("responsetypes2")) + .andExpect(jsonPath("\$.[1].scope").value("scope2")) + .andExpect(jsonPath("\$.[1].subjectType").value("subjecttype2")) + .andExpect(jsonPath("\$.[1].tokenEndpointAuthMethod").value("token2")) + .andExpect(jsonPath("\$.[1].tosUri").value("tosuri2")) + .andExpect(jsonPath("\$.[1].grantType").value("implicit")) + } + + @WithMockUser(value = "someUser", roles = ["USER"]) + def 'GET someuser /DynamicRegistrations return correct responses'() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "testingGroupAAA", applicationType: 'apptypea', + approved: true, contacts: 'contactsa', jwks: 'jwksa', logoUri: 'logouria', policyUri: 'policyuria', + redirectUris: 'redirecturisa', responseTypes: 'responsetypesa', scope: 'scopea', subjectType: 'subjecttypea', + tokenEndpointAuthMethod: 'tokena', tosUri: 'tosuria', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg) + + when: + def result1 = mockMvc.perform(get('/api/DynamicRegistrations')) + + then: + result1.andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)).andExpect(jsonPath("\$").isEmpty()) + + when: + def dynReg2 = new DynamicRegistrationInfo(resourceId: 'uuid-2', enabled: false, applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', name: 'foo', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + dynamicRegistrationService.createNew(new DynamicRegistrationRepresentation(dynReg2)) + def result = mockMvc.perform(get('/api/DynamicRegistrations')) + + then: + result.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$", Matchers.hasSize(1))) + .andExpect(jsonPath("\$.[0].resourceId").value("uuid-2")) + .andExpect(jsonPath("\$.[0].enabled").value(false)) + .andExpect(jsonPath("\$.[0].idOfOwner").value("testingGroupBBB")) + .andExpect(jsonPath("\$.[0].applicationType").value("apptype")) + .andExpect(jsonPath("\$.[0].contacts").value("contacts")) + .andExpect(jsonPath("\$.[0].jwks").value("jwks")) + .andExpect(jsonPath("\$.[0].logoUri").value("logouri")) + .andExpect(jsonPath("\$.[0].policyUri").value("policyuri")) + .andExpect(jsonPath("\$.[0].redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.[0].responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.[0].scope").value("scope")) + .andExpect(jsonPath("\$.[0].subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.[0].tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.[0].tosUri").value("tosuri")) + .andExpect(jsonPath("\$.[0].grantType").value("implicit")) + + try { + mockMvc.perform(get('/api/DynamicRegistration/uuid-1')) + } catch (Exception e) { + e instanceof ForbiddenException + } + + def result2 = mockMvc.perform(get('/api/DynamicRegistration/uuid-2')) + result2.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.resourceId").value("uuid-2")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.idOfOwner").value("testingGroupBBB")) + .andExpect(jsonPath("\$.applicationType").value("apptype")) + .andExpect(jsonPath("\$.contacts").value("contacts")) + .andExpect(jsonPath("\$.jwks").value("jwks")) + .andExpect(jsonPath("\$.logoUri").value("logouri")) + .andExpect(jsonPath("\$.policyUri").value("policyuri")) + .andExpect(jsonPath("\$.redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.scope").value("scope")) + .andExpect(jsonPath("\$.subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.tosUri").value("tosuri")) + .andExpect(jsonPath("\$.grantType").value("implicit")) + } + + @WithMockUser(value = "someUser", roles = ["USER"]) + def 'POST create new '() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-2', enabled: false, applicationType: 'apptype', name: 'foo', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + def driJson = mapper.writeValueAsString(dynReg) + + when: + def result = mockMvc.perform(post('/api/DynamicRegistration').contentType(APPLICATION_JSON).content(driJson)) + + then: + result.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isCreated()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.resourceId").value("uuid-2")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.idOfOwner").value("testingGroupBBB")) + .andExpect(jsonPath("\$.applicationType").value("apptype")) + .andExpect(jsonPath("\$.contacts").value("contacts")) + .andExpect(jsonPath("\$.jwks").value("jwks")) + .andExpect(jsonPath("\$.logoUri").value("logouri")) + .andExpect(jsonPath("\$.policyUri").value("policyuri")) + .andExpect(jsonPath("\$.redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.scope").value("scope")) + .andExpect(jsonPath("\$.subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.tosUri").value("tosuri")) + .andExpect(jsonPath("\$.grantType").value("implicit")) + + } + + @WithMockAdmin + def "PUT /DynamicRegistration updates properly as admin"() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "admingroup", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', name: 'foo', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg) + def result = mockMvc.perform(get('/api/DynamicRegistrations')) + + expect: + result.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.[0].enabled").value(false)) + .andExpect(jsonPath("\$.[0].idOfOwner").value("admingroup")) + .andExpect(jsonPath("\$.[0].applicationType").value("apptype")) + .andExpect(jsonPath("\$.[0].contacts").value("contacts")) + .andExpect(jsonPath("\$.[0].jwks").value("jwks")) + .andExpect(jsonPath("\$.[0].logoUri").value("logouri")) + .andExpect(jsonPath("\$.[0].policyUri").value("policyuri")) + .andExpect(jsonPath("\$.[0].redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.[0].responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.[0].scope").value("scope")) + .andExpect(jsonPath("\$.[0].subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.[0].tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.[0].tosUri").value("tosuri")) + .andExpect(jsonPath("\$.[0].grantType").value("implicit")) + + when: + def dri = repo.findByResourceId("uuid-1") + DynamicRegistrationRepresentation drr = new DynamicRegistrationRepresentation(dri) + drr.setApplicationType("apptype2") + drr.setJwks("jwks2") + drr.setContacts("contacts2") + drr.setEnabled(true) + def drrJson = mapper.writeValueAsString(drr) + def update = mockMvc.perform(put("/api/DynamicRegistration/uuid-1").contentType(APPLICATION_JSON).content(drrJson)) + + then: + update.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.enabled").value(true)) + .andExpect(jsonPath("\$.idOfOwner").value("admingroup")) + .andExpect(jsonPath("\$.applicationType").value("apptype2")) + .andExpect(jsonPath("\$.contacts").value("contacts2")) + .andExpect(jsonPath("\$.jwks").value("jwks2")) + .andExpect(jsonPath("\$.logoUri").value("logouri")) + .andExpect(jsonPath("\$.policyUri").value("policyuri")) + .andExpect(jsonPath("\$.redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.scope").value("scope")) + .andExpect(jsonPath("\$.subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.tosUri").value("tosuri")) + .andExpect(jsonPath("\$.grantType").value("implicit")) + } + + @WithMockUser(value = "someUser", roles = ["USER"]) + def "PUT /DynamicRegistration disallows non-admin user from enabling"() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "testingGroupBBB", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', name: 'foo', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg) + def result = mockMvc.perform(get('/api/DynamicRegistrations')) + + expect: + result.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.[0].enabled").value(false)) + .andExpect(jsonPath("\$.[0].idOfOwner").value("testingGroupBBB")) + .andExpect(jsonPath("\$.[0].applicationType").value("apptype")) + .andExpect(jsonPath("\$.[0].contacts").value("contacts")) + .andExpect(jsonPath("\$.[0].jwks").value("jwks")) + .andExpect(jsonPath("\$.[0].logoUri").value("logouri")) + .andExpect(jsonPath("\$.[0].policyUri").value("policyuri")) + .andExpect(jsonPath("\$.[0].redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.[0].responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.[0].scope").value("scope")) + .andExpect(jsonPath("\$.[0].subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.[0].tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.[0].tosUri").value("tosuri")) + .andExpect(jsonPath("\$.[0].grantType").value("implicit")) + + when: + def dri = repo.findByResourceId("uuid-1") + DynamicRegistrationRepresentation drr = new DynamicRegistrationRepresentation(dri) + drr.setApplicationType("apptype2") + drr.setJwks("jwks2") + drr.setContacts("contacts2") + drr.setEnabled(true) + def drrJson = mapper.writeValueAsString(drr) + + then: + try { + mockMvc.perform(put("/api/DynamicRegistration/uuid-1").contentType(APPLICATION_JSON).content(drrJson)) + } catch (Exception e) { + e instanceof ForbiddenException + } + } + + @WithMockUser(value = "someUser", roles = ["USER"]) + def "PUT /DynamicRegistration denies the request if the PUTing user is not an ADMIN and not in the owner group"() { + when: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "testingGroupAAA", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', name: 'foo', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + def dri = repo.saveAndFlush(dynReg) + + DynamicRegistrationRepresentation drr = new DynamicRegistrationRepresentation(dri) + drr.setApplicationType("apptype2") + def drrJson = mapper.writeValueAsString(drr) + + then: + try { + mockMvc.perform(put("/api/DynamicRegistration/uuid-1").contentType(APPLICATION_JSON).content(drrJson)) + } catch (Exception e) { + e instanceof ForbiddenException + } + } + + @WithMockAdmin + def "PUT /DynamicRegistration update group as admin"() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "AAA", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', name: 'foo', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg) + def result = mockMvc.perform(get('/api/DynamicRegistrations')) + + expect: + result.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.[0].enabled").value(false)) + .andExpect(jsonPath("\$.[0].idOfOwner").value("AAA")) + .andExpect(jsonPath("\$.[0].applicationType").value("apptype")) + .andExpect(jsonPath("\$.[0].contacts").value("contacts")) + .andExpect(jsonPath("\$.[0].jwks").value("jwks")) + .andExpect(jsonPath("\$.[0].logoUri").value("logouri")) + .andExpect(jsonPath("\$.[0].policyUri").value("policyuri")) + .andExpect(jsonPath("\$.[0].redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.[0].responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.[0].scope").value("scope")) + .andExpect(jsonPath("\$.[0].subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.[0].tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.[0].tosUri").value("tosuri")) + .andExpect(jsonPath("\$.[0].grantType").value("implicit")) + + when: + def update = mockMvc.perform(put("/api/DynamicRegistration/uuid-1/changeGroup/testingGroupBBB")) + + then: + update.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.enabled").value(false)) + .andExpect(jsonPath("\$.idOfOwner").value("testingGroupBBB")) + .andExpect(jsonPath("\$.applicationType").value("apptype")) + .andExpect(jsonPath("\$.contacts").value("contacts")) + .andExpect(jsonPath("\$.jwks").value("jwks")) + .andExpect(jsonPath("\$.logoUri").value("logouri")) + .andExpect(jsonPath("\$.policyUri").value("policyuri")) + .andExpect(jsonPath("\$.redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.scope").value("scope")) + .andExpect(jsonPath("\$.subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.tosUri").value("tosuri")) + .andExpect(jsonPath("\$.grantType").value("implicit")) + } + + @WithMockUser(value = "someUser", roles = ["USER"]) + def "PUT /DynamicRegistration update group as user shouldn't work "() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "testingGroupBBB", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', name: 'foo', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repo.saveAndFlush(dynReg) + def result = mockMvc.perform(get('/api/DynamicRegistrations')) + + expect: + result.andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()).andExpect(content().contentType(APPLICATION_JSON)) + .andExpect(jsonPath("\$.[0].resourceId").value("uuid-1")) + .andExpect(jsonPath("\$.[0].enabled").value(false)) + .andExpect(jsonPath("\$.[0].idOfOwner").value("testingGroupBBB")) + .andExpect(jsonPath("\$.[0].applicationType").value("apptype")) + .andExpect(jsonPath("\$.[0].contacts").value("contacts")) + .andExpect(jsonPath("\$.[0].jwks").value("jwks")) + .andExpect(jsonPath("\$.[0].logoUri").value("logouri")) + .andExpect(jsonPath("\$.[0].policyUri").value("policyuri")) + .andExpect(jsonPath("\$.[0].redirectUris").value("redirecturis")) + .andExpect(jsonPath("\$.[0].responseTypes").value("responsetypes")) + .andExpect(jsonPath("\$.[0].scope").value("scope")) + .andExpect(jsonPath("\$.[0].subjectType").value("subjecttype")) + .andExpect(jsonPath("\$.[0].tokenEndpointAuthMethod").value("token")) + .andExpect(jsonPath("\$.[0].tosUri").value("tosuri")) + .andExpect(jsonPath("\$.[0].grantType").value("implicit")) + + try { + def update = mockMvc.perform(put("/api/DynamicRegistration/uuid-1/changeGroup/testingGroupAAA")) + } catch (Exception e) { + e instanceof ForbiddenException + } + } +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorVersionControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorVersionControllerTests.groovy index fd8838b4e..411066916 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorVersionControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorVersionControllerTests.groovy @@ -9,7 +9,6 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationName import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationURL import edu.internet2.tier.shibboleth.admin.ui.envers.EnversVersionServiceSupport import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects -import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/InternationalizationMessagesControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/InternationalizationMessagesControllerTests.groovy index 9287ff552..2afb5116c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/InternationalizationMessagesControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/InternationalizationMessagesControllerTests.groovy @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import edu.internet2.tier.shibboleth.admin.ui.i18n.MappedResourceBundleMessageSource import org.springframework.beans.factory.annotation.Autowired @@ -22,7 +23,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @author Bill Smith (wsmith@unicon.net) */ @DataJpaTest -@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration]) +@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") class InternationalizationMessagesControllerTests extends Specification { @@ -125,4 +126,4 @@ class InternationalizationMessagesControllerTests extends Specification { then: result.andExpect(content().json(expectedDefaultResult)) } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy index 49b0b1f19..387236094 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/controller/GroupsControllerIntegrationTests.groovy @@ -9,8 +9,8 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User import edu.internet2.tier.shibboleth.admin.ui.security.repository.GroupsRepository +import edu.internet2.tier.shibboleth.admin.ui.service.DynamicRegistrationService import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl -import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin import groovy.json.JsonOutput import org.springframework.beans.factory.annotation.Autowired @@ -33,6 +33,9 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { @Autowired GroupsRepository groupsRepository + @Autowired + private DynamicRegistrationService dynamicRegistrationService + @Autowired JPAEntityDescriptorServiceImpl service @@ -45,6 +48,7 @@ class GroupsControllerIntegrationTests extends AbstractBaseDataJpaTest { GroupController groupController = new GroupController().with ({ it.groupService = this.groupService it.entityDescriptorService = this.service + it.dynamicRegistrationService = this.dynamicRegistrationService it }) mockMvc = MockMvcBuilders.standaloneSetup(groupController).build() diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegateTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegateTests.groovy index f3c3ab8fd..99ca11bd5 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegateTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/permission/ShibUiPermissionDelegateTests.groovy @@ -2,13 +2,13 @@ package edu.internet2.tier.shibboleth.admin.ui.security.permission import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor -import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin import org.springframework.beans.factory.annotation.Autowired @@ -20,6 +20,9 @@ import org.springframework.transaction.annotation.Transactional class ShibUiPermissionDelegateTests extends AbstractBaseDataJpaTest { ShibUiPermissionDelegate delegate + @Autowired + DynamicRegistrationInfoRepository dynamicRegistrationInfoRepository + @Autowired JPAEntityDescriptorServiceImpl jpaEntityDescriptorService @@ -29,7 +32,7 @@ class ShibUiPermissionDelegateTests extends AbstractBaseDataJpaTest { @Transactional def setup() { - delegate = new ShibUiPermissionDelegate(entityDescriptorRepository, userService) + delegate = new ShibUiPermissionDelegate(dynamicRegistrationInfoRepository, entityDescriptorRepository, userService) createDevUsersAndGroups() } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceTests.groovy index e51dd33a0..4dfe078e4 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/EntityIdsSearchServiceTests.groovy @@ -3,6 +3,7 @@ package edu.internet2.tier.shibboleth.admin.ui.service import edu.internet2.tier.shibboleth.admin.ui.configuration.CoreShibUiConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.InternationalizationConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.SearchConfiguration +import edu.internet2.tier.shibboleth.admin.ui.configuration.ShibUIConfiguration import edu.internet2.tier.shibboleth.admin.ui.configuration.TestConfiguration import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.domain.EntityScan @@ -15,7 +16,7 @@ import spock.lang.Specification * @author Bill Smith (wsmith@unicon.net) */ @DataJpaTest -@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration]) +@ContextConfiguration(classes=[CoreShibUiConfiguration, SearchConfiguration, TestConfiguration, InternationalizationConfiguration, ShibUIConfiguration]) @EnableJpaRepositories(basePackages = ["edu.internet2.tier.shibboleth.admin.ui"]) @EntityScan("edu.internet2.tier.shibboleth.admin.ui") class EntityIdsSearchServiceTests extends Specification { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImplTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImplTests.groovy new file mode 100644 index 000000000..1bad8fd02 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/service/JPADynamicRegistrationServiceImplTests.groovy @@ -0,0 +1,174 @@ +package edu.internet2.tier.shibboleth.admin.ui.service + +import com.fasterxml.jackson.databind.ObjectMapper +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.configuration.JsonSchemaComponentsConfiguration +import edu.internet2.tier.shibboleth.admin.ui.controller.DynamicRegistrationController +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptorProtocol +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.AssertionConsumerServiceRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ContactRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.KeyDescriptorRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.LogoutEndpointRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.MduiRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.OrganizationRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.SecurityInfoRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.ServiceProviderSsoDescriptorRepresentation +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.DynamicRegistrationInfo +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.GrantType +import edu.internet2.tier.shibboleth.admin.ui.domain.oidc.OAuthRPExtensions +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResourceLocation +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.LowLevelJsonSchemaValidator +import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository +import edu.internet2.tier.shibboleth.admin.ui.security.model.Approvers +import edu.internet2.tier.shibboleth.admin.ui.security.model.Group +import edu.internet2.tier.shibboleth.admin.ui.security.model.Role +import edu.internet2.tier.shibboleth.admin.ui.security.model.User +import edu.internet2.tier.shibboleth.admin.ui.security.repository.DynamicRegistrationInfoRepository +import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator +import edu.internet2.tier.shibboleth.admin.ui.util.TestObjectGenerator +import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin +import edu.internet2.tier.shibboleth.admin.util.EntityDescriptorConversionUtils +import org.skyscreamer.jsonassert.JSONAssert +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.json.JacksonTester +import org.springframework.context.annotation.PropertySource +import org.springframework.core.io.DefaultResourceLoader +import org.springframework.mock.http.MockHttpInputMessage +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.web.servlet.result.MockMvcResultHandlers +import org.springframework.test.web.servlet.setup.MockMvcBuilders +import org.springframework.transaction.annotation.Transactional +import org.xmlunit.builder.DiffBuilder +import org.xmlunit.builder.Input +import org.xmlunit.diff.DefaultNodeMatcher +import org.xmlunit.diff.ElementSelectors +import spock.lang.Ignore + +import java.time.LocalDateTime + +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.metadataSourcesOIDCSchema +import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaLocationLookup.metadataSourcesSAMLSchema +import static org.springframework.http.MediaType.APPLICATION_JSON +import static org.springframework.http.MediaType.APPLICATION_JSON +import static org.springframework.http.MediaType.APPLICATION_JSON +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +@PropertySource("classpath:application.yml") +class JPADynamicRegistrationServiceImplTests extends AbstractBaseDataJpaTest { + @Autowired + EntityService entityService + + @Autowired + ObjectMapper mapper + + @Autowired + JPADynamicRegistrationServiceImpl service + + @Autowired + TestObjectGenerator testObjectGenerator + + RandomGenerator generator + JacksonTester jacksonTester + + @Autowired + DynamicRegistrationInfoRepository repository + +// @Transactional +// def setup() { +// Group ga = new Group() +// ga.setResourceId("testingGroupAAA") +// ga.setName("Group AAA") +// ga = groupService.createGroup(ga) +// +// Group gb = new Group() +// gb.setResourceId("testingGroupBBB") +// gb.setName("Group BBB") +// gb = groupService.createGroup(gb) +// +// Optional userRole = roleRepository.findByName("ROLE_USER") +// User user = new User(username: "someUser", roles:[userRole.get()], password: "foo") +// user.setGroup(gb) +// userService.save(user) +// } + + @WithMockAdmin + def 'getDisabledDynamicRegistrations'() { + given: + def dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-1', enabled: false, idOfOwner: "admingroup", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repository.saveAndFlush(dynReg) + + when: + List dris = service.getDisabledDynamicRegistrations() + + then: + dris.size() == 1 + + when: + dynReg = new DynamicRegistrationInfo(resourceId: 'uuid-2', enabled: true, idOfOwner: "admingroup", applicationType: 'apptype', + approved: true, contacts: 'contacts', jwks: 'jwks', logoUri: 'logouri', policyUri: 'policyuri', + redirectUris: 'redirecturis', responseTypes: 'responsetypes', scope: 'scope', subjectType: 'subjecttype', + tokenEndpointAuthMethod: 'token', tosUri: 'tosuri', grantType: GrantType.implicit) + repository.saveAndFlush(dynReg) + + then: + service.getDisabledDynamicRegistrations().size() == 1 + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3f81b89cc..c23f11804 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,6 +24,7 @@ postgresVersion=42.3.4 sqlserverVersion=9.4.1.jre11 org.gradle.jvmargs=-Xmx1g -XX:-UseGCOverheadLimit +org.gradle.console=verbose # set token in personal global i2.github.token= @@ -33,6 +34,12 @@ i2.github.apiEndpoint=https://github.internet2.edu/api/v3 i2.git.remote=i2 i2.git.branch=master +# set app +use.release.app.yml=false + +### Fix for creating the docker image on Macs +org.gradle.daemon=false + ## NOTES # pac4j spring security 7.0.3 here uses the pac4j 5.4.3 core, thus differences in versions (they used use the same versions, now # keeping them in sync takes paying attention \ No newline at end of file diff --git a/testbed/authentication/shibboleth-idp/Dockerfile b/testbed/authentication/shibboleth-idp/Dockerfile index 2b10847f0..ed663d8b5 100644 --- a/testbed/authentication/shibboleth-idp/Dockerfile +++ b/testbed/authentication/shibboleth-idp/Dockerfile @@ -1,4 +1,4 @@ -FROM tier/shib-idp:4.0.0_20200518 +FROM i2incommon/shib-idp:4.2.1_20220624 # The build args below can be used at build-time to tell the build process where to find your config files. This is for a completely burned-in config. ARG TOMCFG=config/tomcat diff --git a/testbed/authentication/shibboleth-idp/config/shib-idp/conf/relying-party.xml b/testbed/authentication/shibboleth-idp/config/shib-idp/conf/relying-party.xml index 5127515ed..f216e2f5e 100644 --- a/testbed/authentication/shibboleth-idp/config/shib-idp/conf/relying-party.xml +++ b/testbed/authentication/shibboleth-idp/config/shib-idp/conf/relying-party.xml @@ -40,7 +40,7 @@ --> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testbed/integration/shibboleth-idp/config/shib-idp/conf/global.xml b/testbed/integration/shibboleth-idp/config/shib-idp/conf/global.xml new file mode 100644 index 000000000..ce4d699e6 --- /dev/null +++ b/testbed/integration/shibboleth-idp/config/shib-idp/conf/global.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testbed/integration/shibboleth-idp/config/shib-idp/conf/idp.properties b/testbed/integration/shibboleth-idp/config/shib-idp/conf/idp.properties index 50af60005..b356a4574 100644 --- a/testbed/integration/shibboleth-idp/config/shib-idp/conf/idp.properties +++ b/testbed/integration/shibboleth-idp/config/shib-idp/conf/idp.properties @@ -1,5 +1,5 @@ # Load any additional property resources from a comma-delimited list -idp.additionalProperties=/conf/ldap.properties, /conf/saml-nameid.properties, /conf/services.properties, /conf/authn/duo.properties, /credentials/secrets.properties +idp.additionalProperties=/conf/ldap.properties, /conf/saml-nameid.properties, /conf/services.properties, /conf/authn/duo.properties, /credentials/secrets.properties, /conf/oidc.properties # In most cases (and unless noted in the surrounding comments) the # commented settings in the distributed files document default behavior. @@ -224,3 +224,5 @@ idp.ui.fallbackLanguages=en,fr,de # Set false if you want SAML bindings "spelled out" in audit log idp.audit.shortenBindings=true + +#idp.loglevel.idp=DEBUG diff --git a/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-attribute-filter.xml b/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-attribute-filter.xml new file mode 100644 index 000000000..e1c7e4aed --- /dev/null +++ b/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-attribute-filter.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-attribute-resolver.xml b/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-attribute-resolver.xml new file mode 100644 index 000000000..bab9f96ae --- /dev/null +++ b/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-attribute-resolver.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + member + staff + + + +1 (604) 555-1234;ext=5678 + + + true + + + false + + + Mr.Teppo Matias Testaaja + + + Testaaja + + + Teppo Matias + + + Matias + + + TT + + + https://fi.wikipedia.org/wiki/Tom_Cruise + + + https://pixabay.com/fi/pentu-kissa-kukka-potin-tabby-pentu-2766820/ + + + https://www.facebook.com/officialtomcruise/ + + + male + + + 1969-07-20 + + + America/Los_Angeles + + + en-US + + + 1509450347 + + + 234 Hollywood Blvd. + + + Los Angeles + + + CA + + + 90210 + + + US + + + + diff --git a/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-clientinfo-resolvers.xml b/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-clientinfo-resolvers.xml new file mode 100644 index 000000000..425fa6b73 --- /dev/null +++ b/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc-clientinfo-resolvers.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + diff --git a/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc.properties b/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc.properties new file mode 100644 index 000000000..91c3d731d --- /dev/null +++ b/testbed/integration/shibboleth-idp/config/shib-idp/conf/oidc.properties @@ -0,0 +1,146 @@ +# Set the Open ID Connect Issuer value +idp.oidc.issuer = https://idp.unicon.local + +#Dynamic registration properties +idp.oidc.dynreg.StorageService=shibboleth.JPAStorageService + +# The validity of registration before a new one is required. +#idp.oidc.dynreg.defaultRegistrationValidity = PT24H +# The validity of client secret registered +#idp.oidc.dynreg.defaultSecretExpiration = P12M +# The default scopes accepted in dynamic registration +#idp.oidc.dynreg.defaultScope = openid profile email address phone offline_access +# The default subject type if not set by client in request. Maybe set to pairwise or public. +#idp.oidc.dynreg.defaultSubjectType = public +# The acceptable client authentication methods when using dynamic registration +#idp.oidc.dynreg.tokenEndpointAuthMethods = client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt +# Regardless of what signing algorithms are configured, allow none for request object signing +#idp.oidc.dynreg.allowNoneForRequestSigning = true +# Bean to determine whether dynamic registration should validate the remote JWK set if it's defined in the request +#idp.oidc.dynreg.validateRemoteJwks = shibboleth.Conditions.TRUE +# Full path to the file containing default metadata policy used for dynamic client registration +#idp.oidc.dynreg.defaultMetadataPolicyFile = +# Bean to determine the default metadata policy used for dynamic client registration +#idp.oidc.dynreg.defaultMetadataPolicy = shibboleth.oidc.dynreg.DefaultMetadataPolicy + +# Storage for storing remote jwk sets. +#idp.oidc.jwk.StorageService = shibboleth.StorageService + +#Authorization/Token endpoint properties +# The acceptable client authentication methods +#idp.oidc.tokenEndpointAuthMethods = client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt + +# Default lifetime of OIDC tokens (issued to the client or against the OP itself) +#idp.oidc.authorizeCode.defaultLifetime = PT5M +#idp.oidc.accessToken.defaultLifetime = PT10M +#idp.oidc.refreshToken.defaultLifetime = PT2H +#idp.oidc.idToken.defaultLifetime = PT1H + +# Lifetime of entries in revocation cache for authorize code +#idp.oidc.revocationCache.authorizeCode.lifetime = PT6H +# Storage for revocation cache. Requires server-side storage +#idp.oidc.revocationCache.StorageService = shibboleth.StorageService + +# Signing keys for id tokens / userinfo response +idp.signing.oidc.rs.key = %{idp.home}/credentials/idp-signing-rs.jwk +idp.signing.oidc.es.key = %{idp.home}/credentials/idp-signing-es.jwk +# Request object decryption key +idp.signing.oidc.rsa.enc.key = %{idp.home}/credentials/idp-encryption-rsa.jwk + +# Set false to preclude issuing unencrypted ID/UserInfo tokens without specific overrides +#idp.oidc.encryptionOptional = true + +#PKCE/AppAuth related properties +#idp.oidc.forcePKCE = false +#idp.oidc.allowPKCEPlain = false + +# Store user consent to authorization code & access/refresh tokens instead of exploiting consent storage +#idp.oidc.encodeConsentInTokens = false + +# shibboleth.ClientInformationResolverService properties +#idp.service.clientinfo.failFast = false +#idp.service.clientinfo.checkInterval = PT0S +#idp.service.clientinfo.resources = shibboleth.ClientInformationResolverResources + +# Special claim handling rules +# "Encoded" attributes are encrypted and embedded into the access token +#idp.oidc.encodedAttributes = +# "Always included" attributes are forced into ID tokens for all response_types +#idp.oidc.alwaysIncludedAttributes = +# "Denied" attributes are omitted from the UserInfo token +#idp.oidc.deniedUserInfoAttributes = + +# The source attribute used in generating the sub claim +idp.oidc.subject.sourceAttribute = uid + +# The digest algorithm used in generating the sub claim +#idp.oidc.subject.algorithm = SHA + +# The salt used in generating the subject +# Do *NOT* share the salt with other people, it's like divulging your private key. +# It is suggested you move this property into credentials/secrets.properties +idp.oidc.subject.salt = eezien3iteit0gaiciiweayohxahmai6 + +# Bean to determine whether SAML metadata should be exploited for trusted OIDC RP resolution +#idp.oidc.metadata.saml = shibboleth.Conditions.TRUE + +# Upgrade interval to the remote JWKs +#idp.oidc.jwksuri.fetchInterval = PT30M + +# Bounds on the next file refresh of the OP configuration resource +#idp.oidc.config.minRefreshDelay = PT5M +#idp.oidc.config.maxRefreshDelay = PT4H + +# Bean to configure additional response headers: none is added by default, but e.g. shibboleth.ResponseHeaderFilter +# contains headers further configurable via other properties such as 'idp.hsts', 'idp.frameoptions' and 'idp.csp'. +#idp.oidc.ResponseHeaderFilter = shibboleth.ResponseHeaderFilter + +# Bean used for extracting login_hint from the authentication request. The default function parses login_hint as is. +#idp.oidc.LoginHintLookupStrategy = DefaultRequestLoginHintLookupFunction + +# Bean used for creating SPSessions needed for SLO. By default builds protocol-independent BasicSPSession, as SLO is not yet supported. +#idp.oidc.SPSessionCreationStrategy = DefaultSPSessionCreationStrategy + +# Settings for issue-registration-access-token flow +#idp.oidc.admin.registration.logging = IssueRegistrationAccessToken +idp.oidc.admin.registration.nonBrowserSupported = true +idp.oidc.admin.registration.authenticated = false +#idp.oidc.admin.registration.resolveAttributes = false +#idp.oidc.admin.registration.lookup.policy = shibboleth.oidc.admin.DefaultMetadataPolicyLookupStrategy +#idp.oidc.admin.registration.defaultTokenLifetime = P1D +idp.oidc.admin.registration.accessPolicy = AccessByIPAddress +#idp.oidc.admin.registration.policyLocationPolicy = AccessByAdmin +idp.oidc.admin.registration.policyIdPolicy = AccessByIPAddress +#idp.oidc.admin.registration.clientIdPolicy = AccessByAdmin + +idp.oidc.admin.clients.authenticated = false +idp.oidc.admin.clients.accessPolicy = AccessByIPAddress + +# +# OAuth2 Settings - these typically involve generic OAuth 2.0 use cases +# + +# Supported grant_type values for token requests +#idp.oauth2.grantTypes = authorization_code,refresh_token + +# Default handling of generic OAuth tokens (for use against arbitrary resource servers) +#idp.oauth2.accessToken.defaultLifetime = PT10M +# Set to JWT if desired as a default. +#idp.oauth2.accessToken.type = + +# Set false to preclude issuing unencrypted JWT access tokens without specific overrides +#idp.oauth2.encryptionOptional = true + +# Default scope/audience values if you allow unverified clients without metadata. +#idp.oauth2.defaultAllowedScope = +#idp.oauth2.defaultAllowedAudience = + +# Regular expression matching OAuth login flows to enable. +# For most deployments, the default is sufficient to accomodate a variety of methods +#idp.oauth2.authn.flows = OAuth2Client + +# Set true to enforce refresh token rotation (defaults to false) +#idp.oauth2.enforceRefreshTokenRotation = true + +# Revocation method: set to TOKEN to revoke single tokens (defaults to full chain (value = CHAIN)) +#idp.oauth2.revocationMethod = TOKEN \ No newline at end of file diff --git a/testbed/integration/shibboleth-idp/config/shib-idp/conf/relying-party.xml b/testbed/integration/shibboleth-idp/config/shib-idp/conf/relying-party.xml index 478731ac5..9227a1dfe 100644 --- a/testbed/integration/shibboleth-idp/config/shib-idp/conf/relying-party.xml +++ b/testbed/integration/shibboleth-idp/config/shib-idp/conf/relying-party.xml @@ -23,6 +23,8 @@ + + @@ -51,6 +53,11 @@ + + + + + diff --git a/testbed/integration/shibboleth-idp/config/shib-idp/conf/services.xml b/testbed/integration/shibboleth-idp/config/shib-idp/conf/services.xml index c38ff2aa3..da13a677a 100644 --- a/testbed/integration/shibboleth-idp/config/shib-idp/conf/services.xml +++ b/testbed/integration/shibboleth-idp/config/shib-idp/conf/services.xml @@ -26,6 +26,7 @@ %{idp.home}/conf/attribute-resolver.xml + %{idp.home}/conf/oidc-attribute-resolver.xml + + + + +