From d6eb71a154628194d9ff969aed82229d99ea481b Mon Sep 17 00:00:00 2001 From: chasegawa Date: Thu, 25 May 2023 10:14:53 -0700 Subject: [PATCH 1/8] SHIBUI-2571 Intermediate work commit - feature incomplete --- backend/src/main/app-resources/default.yml | 14 ++++ .../admin/ui/beacon/BeaconDetail.java | 21 ++++++ .../admin/ui/beacon/ShibuiDetail.java | 21 ++++++ .../CoreShibUiConfiguration.java | 21 ++++++ .../configuration/auto/WebSecurityConfig.java | 2 +- .../BeaconConfigurationStartupProcessing.java | 68 +++++++++++++++++++ .../ui/controller/RootUiViewController.java | 7 ++ .../admin/ui/domain/BeaconConfiguration.java | 24 +++++++ .../BeaconConfigurationRepository.java | 8 +++ .../ui/scheduled/BeaconReportingTask.java | 25 +++++++ .../ui/service/BeaconDataServiceImpl.java | 33 +++++++++ .../admin/ui/service/IBeaconDataService.java | 7 ++ .../src/main/resources/application.properties | 6 +- .../ui/configuration/TestConfiguration.groovy | 7 ++ ...ConfigurationStartupProcessingTests.groovy | 39 +++++++++++ ...efinitionControllerIntegrationTests.groovy | 2 +- .../EntitiesControllerIntegrationTests.groovy | 3 - ...lerSchemaValidationIntegrationTests.groovy | 4 +- ...efinitionControllerIntegrationTests.groovy | 2 +- ...taFiltersControllerIntegrationTests.groovy | 3 +- ...lerSchemaValidationIntegrationTests.groovy | 4 +- ...tionOrderControllerIntegrationTests.groovy | 2 +- ...lerSchemaValidationIntegrationTests.groovy | 5 +- .../ShibPropertiesControllerTests.groovy | 2 - 24 files changed, 312 insertions(+), 18 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/postprocessors/BeaconConfigurationStartupProcessing.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BeaconConfiguration.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/BeaconConfigurationRepository.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/BeaconReportingTask.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/IBeaconDataService.java create mode 100644 backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/postprocessors/BeaconConfigurationStartupProcessingTests.groovy diff --git a/backend/src/main/app-resources/default.yml b/backend/src/main/app-resources/default.yml index 949a8e4b5..5ba247176 100644 --- a/backend/src/main/app-resources/default.yml +++ b/backend/src/main/app-resources/default.yml @@ -19,6 +19,20 @@ ## The first time the scheduler executes on this node, write out the entity descriptors to file [true|false] (default is true) # entityDescriptor: # writeOnStartup: true +### BEACON SETTINGS +## Set enabled to false to disable sending beacon data +## Only set the installationID if you wish to define the ID used by the beacon. RECOMMENDED: ignore - a default random value will be used +## Only change the urls if instructed or if you wish to redirect for testing (if multiple, separate list with commas +## Only change the productName for testing purposes +## Set the cron time to send the beacon data at a specific time - if unset, the system will send at a random time between 12AM and 4AM once per day +# beacon: +# enabled: ture +# installationID: [user-defined value] +# urls: http://collector.testbed.tier.internet2.edu:5001 +# productName: ShibUI +# send: +# cron: 0 4 * * * * + # pac4j-enabled: true # pac4j: # keystorePath: "/etc/shibui/samlKeystore.jks" diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java new file mode 100644 index 000000000..24c9ccbc4 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java @@ -0,0 +1,21 @@ +package edu.internet2.tier.shibboleth.admin.ui.beacon; + +import lombok.Data; + +@Data +public class BeaconDetail { + private String msgType = "TIERBEACON"; + private String msgName = "TIER"; + private String msgVersion = "1.0"; + + private String tbProduct; + private String tbProductVersion; + private String tbTIERRelease; + private String tbMaintainer = "Unicon"; + + private ShibuiDetail shibui; +} + +// "tbProduct": "ShibUI (shibui.beacon.productName)", +// "tbProductVersion": "1.17.4 (can we get this from the app?)", +// "tbTIERRelease": "(if the TAP container: 1.17.4; if the Unicon package, INTERNAL_1.17.4)", \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java new file mode 100644 index 000000000..fdf0abf6b --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java @@ -0,0 +1,21 @@ +package edu.internet2.tier.shibboleth.admin.ui.beacon; + +import lombok.Data; + +@Data +public class ShibuiDetail { + + + // "shibui": { + // "authMechanisms": [ + // "pac4j-saml" + // ], + // "numberOfMetadataSources": 25, + // "numberOfMetadataProviders": 3, + // "numberOfFilters": 0, + // "dailyLogins": 12, + // "numberOfGroups": 15, + // "numberOfRoles": 5, + // "installationID": "4813c762-4a90-40a2-9c01-81bf67647da4" + // } +} \ 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 97444ac4d..2012f9558 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 @@ -1,7 +1,9 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration; import com.fasterxml.jackson.databind.Module; +import edu.internet2.tier.shibboleth.admin.ui.domain.BeaconConfiguration; import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; +import edu.internet2.tier.shibboleth.admin.ui.repository.BeaconConfigurationRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolversPositionOrderContainerRepository; @@ -19,6 +21,7 @@ import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository; import edu.internet2.tier.shibboleth.admin.ui.security.service.IGroupService; import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; +import edu.internet2.tier.shibboleth.admin.ui.service.BeaconDataServiceImpl; 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; @@ -29,6 +32,7 @@ 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.IBeaconDataService; 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; @@ -41,10 +45,12 @@ import edu.internet2.tier.shibboleth.admin.util.ModelRepresentationConversions; import net.javacrumbs.shedlock.core.LockProvider; import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; +import org.apache.commons.lang3.StringUtils; 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.actuate.info.InfoEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; @@ -64,6 +70,7 @@ import javax.servlet.http.HttpServletRequest; import javax.sql.DataSource; +import java.util.Optional; @Configuration @Import(SearchConfiguration.class) @@ -254,4 +261,18 @@ public DynamicRegistrationService dynamicRegistrationService(DynamicRegistration public LockProvider lockProvider(DataSource dataSource) { return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder().withJdbcTemplate(new JdbcTemplate(dataSource)).usingDbTime().build()); } + + @Bean + public String getBeaconCronValue(BeaconConfigurationRepository repo) + { + Optional bc = repo.findById(1); + return bc.isPresent() ? bc.get().getSendCron() : "0 3 * * * *"; + } + + @Bean + public IBeaconDataService getBeaconDataService(@Value("${shibui.beacon.productName:ShibUi}") String productName, InfoEndpoint info, @Value("#{environment.TIERVERSION}") String tierVersion) { +// productName = StringUtils.isBlank(productName) ? "ShibUI" : productName; + BeaconDataServiceImpl result = new BeaconDataServiceImpl(productName, info); + return result; + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java index f2135109e..6e19098dd 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java @@ -88,7 +88,7 @@ protected void configure(HttpSecurity http) throws Exception { .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .authorizeRequests() - .antMatchers("/unsecured/**/*","/entities/**/*").permitAll() + .antMatchers("/unsecured/**/*","/entities/**/*", "/health").permitAll() .anyRequest().hasAnyRole(acceptedAuthenticationRoles) .and() .exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> response.sendRedirect("/unsecured/error.html")) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/postprocessors/BeaconConfigurationStartupProcessing.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/postprocessors/BeaconConfigurationStartupProcessing.java new file mode 100644 index 000000000..ee7e1a1e9 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/postprocessors/BeaconConfigurationStartupProcessing.java @@ -0,0 +1,68 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration.postprocessors; + +import edu.internet2.tier.shibboleth.admin.ui.domain.BeaconConfiguration; +import edu.internet2.tier.shibboleth.admin.ui.repository.BeaconConfigurationRepository; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import javax.persistence.EntityExistsException; +import javax.transaction.Transactional; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +/** + * This approach for running logic waits until after the Spring context has been initialized. + * We will always use an ID of 1 for the beaconConfiguration, so if the data doesn't exist, we can try to write the data. If the + * data exists, we don't need to do anything. + * + * Because multiple instances could be starting at the same time, we must account for a save "losing" to another entity - that's ok + * and if we get an exception because the entity with the id already exists, we can just bail out. + */ +@Component +public class BeaconConfigurationStartupProcessing { + @Autowired + BeaconConfigurationRepository beaconRepo; + + @EventListener + @Transactional + public void onApplicationEvent(ContextRefreshedEvent event) { + // If there is nothing in the db, create the entry to use going forward + if (beaconRepo.count() == 0) { + String appId = event.getApplicationContext().getEnvironment().getProperty("shibui.beacon.installationID"); + BeaconConfiguration bc = new BeaconConfiguration(); + bc.setInstallationId(StringUtils.isBlank(appId) ? UUID.randomUUID().toString() : appId); + + String cronDef = event.getApplicationContext().getEnvironment().getProperty("shibui.beacon.send.cron"); + + // If not set, set a random time between 12AM-4AM + if (StringUtils.isBlank(cronDef)) { + int randomMin = ThreadLocalRandom.current().nextInt(0, 60); + int randomHour = ThreadLocalRandom.current().nextInt(0, 5); + cronDef = "" + randomMin + " " + randomHour + " * * * *"; + } + + bc.setSendCron(cronDef); + + try { + beaconRepo.save(bc); + } + catch (EntityExistsException ignore) { + // Race between startup instances - as long as one of them won... + } + } + // if the entry exists, check that the cron timing hasn't changed + else { + String cronDef = event.getApplicationContext().getEnvironment().getProperty("shibui.beacon.send.cron"); + if (StringUtils.isNotBlank(cronDef)) { + BeaconConfiguration bc = beaconRepo.getReferenceById(1); + if (StringUtils.isNotBlank(cronDef) && !cronDef.equals(bc.getSendCron())) { + bc.setSendCron(cronDef); + beaconRepo.save(bc); + } + } + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/RootUiViewController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/RootUiViewController.java index 80c861898..41d7919c0 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/RootUiViewController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/RootUiViewController.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.info.InfoEndpoint; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; @@ -18,6 +19,7 @@ @Controller public class RootUiViewController { + @Autowired HealthEndpoint healthEndpoint; @Autowired InfoEndpoint infoEndpoint; @@ -46,4 +48,9 @@ public void indexHtml(HttpServletRequest request, HttpServletResponse response) writer.write(content.getBytes()); } } + + @GetMapping(value = "/health") + public ResponseEntity getHealth() { + return ResponseEntity.ok(healthEndpoint.health()); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BeaconConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BeaconConfiguration.java new file mode 100644 index 000000000..93d76fdc3 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BeaconConfiguration.java @@ -0,0 +1,24 @@ +package edu.internet2.tier.shibboleth.admin.ui.domain; + +import lombok.Data; + +import javax.persistence.Entity; +import javax.persistence.Id; + +@Data +@Entity +public class BeaconConfiguration { + @Id + private int id = 1; + + private String installationId; + + private String sendCron; + + /** + * enforce that id will only ever be 1 + */ + public void setId(int ignored) { + this.id = 1; + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/BeaconConfigurationRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/BeaconConfigurationRepository.java new file mode 100644 index 000000000..346a6566e --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/BeaconConfigurationRepository.java @@ -0,0 +1,8 @@ +package edu.internet2.tier.shibboleth.admin.ui.repository; + +import edu.internet2.tier.shibboleth.admin.ui.domain.BeaconConfiguration; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BeaconConfigurationRepository extends JpaRepository { + +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/BeaconReportingTask.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/BeaconReportingTask.java new file mode 100644 index 000000000..c87d7017c --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/BeaconReportingTask.java @@ -0,0 +1,25 @@ +package edu.internet2.tier.shibboleth.admin.ui.scheduled; + +import edu.internet2.tier.shibboleth.admin.ui.service.IBeaconDataService; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.transaction.annotation.Transactional; + +@Configuration +@ConditionalOnProperty(name = "shibui.beacon.enabled", matchIfMissing=true) +@EnableSchedulerLock(defaultLockAtMostFor = "${shibui.maxTask.lockTime:30m}") +public class BeaconReportingTask { + @Autowired + IBeaconDataService dataService; + + @Scheduled(cron="#{@getBeaconCronValue}") + @SchedulerLock(name = "generateEntityDescriptorFiles") + @Transactional(readOnly = true) + public void generateEntityDescriptorFiles() { + + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java new file mode 100644 index 000000000..b9f97ea9a --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java @@ -0,0 +1,33 @@ +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.beacon.BeaconDetail; +import lombok.SneakyThrows; +import org.springframework.boot.actuate.info.InfoEndpoint; + +import java.util.Map; + +public class BeaconDataServiceImpl implements IBeaconDataService { + private ObjectMapper mapper; + private String productName; + private String version; + + public BeaconDataServiceImpl(String productName, InfoEndpoint info) { + mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // skip any null values + + this.productName = productName; + version = info.info().get("build") == null ? "unknown" : ((Map)info.info().get("build")).get("version").toString(); + } + + @Override + @SneakyThrows + public String getBeaconData() throws JsonProcessingException { + BeaconDetail detail = new BeaconDetail(); + detail.setTbProduct(productName); + detail.setTbProductVersion(version); + return mapper.writeValueAsString(detail); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/IBeaconDataService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/IBeaconDataService.java new file mode 100644 index 000000000..4509b0584 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/IBeaconDataService.java @@ -0,0 +1,7 @@ +package edu.internet2.tier.shibboleth.admin.ui.service; + +import com.fasterxml.jackson.core.JsonProcessingException; + +public interface IBeaconDataService { + String getBeaconData() throws JsonProcessingException; +} \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 42f801894..90472bf0e 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -119,7 +119,9 @@ shibui.roles.authenticated=ADMIN,ENABLE,USER #This property must be set to true in order to enable posting stats to beacon endpoint. Furthermore, appropriate #environment variables must be set for beacon publisher to be used (the ones that are set when running shib-ui in #docker container -shibui.beacon-enabled=true +shibui.beacon.enabled=true +shibui.beacon.productName=ShibUi +shibui.beacon.installationID=UNICON-SHIBUI-TESTING ### Swagger/Springdoc patterns springdoc.use-management-port=true @@ -127,7 +129,7 @@ springdoc.swagger-ui.tagsSorter: alpha springdoc.writer-with-order-by-keys: true springdoc.pathsToMatch=/entities, /api/** # This property enables the openapi and swagger-ui endpoints to be exposed beneath the actuator base path. -management.endpoints.web.exposure.include=openapi, swagger-ui, info +management.endpoints.web.exposure.include=openapi, swagger-ui, info, health management.server.port=9090 management.endpoints.web.cors.allowed-origins=* management.endpoints.web.cors.allowed-headers=* \ 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 c41bee896..9b55239ca 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,8 @@ 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.actuate.info.InfoContributor +import org.springframework.boot.actuate.info.InfoEndpoint import org.springframework.boot.web.client.RestTemplateBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -135,4 +137,9 @@ class TestConfiguration { return it } } + + @Bean + public InfoEndpoint getInfoEndpoint() { + return new InfoEndpoint(new ArrayList()); + } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/postprocessors/BeaconConfigurationStartupProcessingTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/postprocessors/BeaconConfigurationStartupProcessingTests.groovy new file mode 100644 index 000000000..ed14ab8a2 --- /dev/null +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/postprocessors/BeaconConfigurationStartupProcessingTests.groovy @@ -0,0 +1,39 @@ +package edu.internet2.tier.shibboleth.admin.ui.configuration.postprocessors + +import edu.internet2.tier.shibboleth.admin.ui.AbstractBaseDataJpaTest +import edu.internet2.tier.shibboleth.admin.ui.domain.BeaconConfiguration +import edu.internet2.tier.shibboleth.admin.ui.repository.BeaconConfigurationRepository +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.ApplicationContext +import org.springframework.context.event.ContextRefreshedEvent +import org.springframework.core.env.Environment + +import static org.mockito.Mockito.mock +import static org.mockito.Mockito.when + +class BeaconConfigurationStartupProcessingTests extends AbstractBaseDataJpaTest { + @Autowired + BeaconConfigurationRepository repo + + def 'test'() { + when: + def event = mock(ContextRefreshedEvent) + def appContext = mock(ApplicationContext) + def env = mock(Environment) + when(event.getApplicationContext()).thenReturn(appContext) + when(appContext.getEnvironment()).thenReturn(env) + when(env.getProperty("shibui.beacon.installationID")).thenReturn(null) + when(env.getProperty("shibui.beacon.send.cron")).thenReturn(null) + + BeaconConfigurationStartupProcessing testObject = new BeaconConfigurationStartupProcessing().with { + it.beaconRepo = repo + it + } + testObject.onApplicationEvent(event) + + then: + BeaconConfiguration bc = repo.getReferenceById(1) + bc.getInstallationId() != null + bc.getSendCron() != null + } +} \ No newline at end of file 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 45eed2ea1..d1a31e703 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 @@ -26,7 +26,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResour /** * @author Dmitriy Kopylenko */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["management.endpoints.web.exposure.include=info, health", "shibui.beacon.productName=shibuiProdName"]) @ActiveProfiles(["no-auth", "badjson"]) class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Specification { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy index 2843711c0..1da168e00 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy @@ -11,7 +11,6 @@ import edu.internet2.tier.shibboleth.admin.ui.security.model.Group import edu.internet2.tier.shibboleth.admin.ui.security.model.Ownership 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.service.EntityDescriptorVersionService import edu.internet2.tier.shibboleth.admin.ui.service.EntityService import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator @@ -62,8 +61,6 @@ class EntitiesControllerIntegrationTests extends AbstractBaseDataJpaTest { @Subject def controller - EntityDescriptorVersionService versionService = Mock() - @Transactional def setup() { openSamlObjects.init() diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerSchemaValidationIntegrationTests.groovy index 39e630223..3dc9fee6e 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerSchemaValidationIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerSchemaValidationIntegrationTests.groovy @@ -8,7 +8,7 @@ import org.springframework.http.HttpHeaders import org.springframework.test.context.ActiveProfiles import spock.lang.Specification -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["management.endpoints.web.exposure.include=info, health", "shibui.beacon.productName=shibuiProdName"]) @ActiveProfiles(["no-auth", "dev"]) class EntityDescriptorControllerSchemaValidationIntegrationTests extends Specification { @@ -51,4 +51,4 @@ class EntityDescriptorControllerSchemaValidationIntegrationTests extends Specifi private static HttpEntity createRequestHttpEntityFor(Closure jsonBodySupplier) { new HttpEntity(jsonBodySupplier(), ['Content-Type': 'application/json'] as HttpHeaders) } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy index c673956fe..07bcefff5 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -9,7 +9,7 @@ import spock.lang.Specification /** * @author Dmitriy Kopylenko */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["management.endpoints.web.exposure.include=info, health", "shibui.beacon.productName=shibuiProdName"]) @ActiveProfiles("no-auth") class GoodJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Specification { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy index 41ed176ad..c5a843b22 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerIntegrationTests.groovy @@ -32,7 +32,8 @@ import static org.springframework.http.HttpMethod.PUT /** * @author Dmitriy Kopylenko */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["management.endpoints.web.exposure.include=info, health", + "shibui.beacon.productName=shibuiProdName"]) @ActiveProfiles(["no-auth", "mfci-test"]) class MetadataFiltersControllerIntegrationTests extends Specification { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy index 32169b063..5dbc39d64 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersControllerSchemaValidationIntegrationTests.groovy @@ -11,7 +11,7 @@ import org.springframework.http.HttpHeaders import org.springframework.test.context.ActiveProfiles import spock.lang.Specification -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["management.endpoints.web.exposure.include=info, health", "shibui.beacon.productName=shibuiProdName"]) @ActiveProfiles(["no-auth", "dev"]) class MetadataFiltersControllerSchemaValidationIntegrationTests extends Specification { @@ -99,4 +99,4 @@ class MetadataFiltersControllerSchemaValidationIntegrationTests extends Specific private static resourceUriFor(String uriTemplate, String resourceId) { String.format(uriTemplate, resourceId) } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy index 6e6ec83cb..e1ed71aa4 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataFiltersPositionOrderControllerIntegrationTests.groovy @@ -12,7 +12,7 @@ import spock.lang.Specification /** * @author Dmitriy Kopylenko */ -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["management.endpoints.web.exposure.include=info, health", "shibui.beacon.productName=shibuiProdName"]) @ActiveProfiles("no-auth") class MetadataFiltersPositionOrderControllerIntegrationTests extends Specification { diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy index d26b90cb5..1fc0374f8 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolverControllerSchemaValidationIntegrationTests.groovy @@ -8,7 +8,8 @@ import org.springframework.http.HttpHeaders import org.springframework.test.context.ActiveProfiles import spock.lang.Specification -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = ["management.endpoints.web.exposure.include=info, health", + "shibui.beacon.productName=shibuiProdName"]) @ActiveProfiles(["no-auth", "dev"]) class MetadataResolverControllerSchemaValidationIntegrationTests extends Specification { @@ -148,4 +149,4 @@ class MetadataResolverControllerSchemaValidationIntegrationTests extends Specifi } -} +} \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy index 3a96695a7..cd405576e 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/ShibPropertiesControllerTests.groovy @@ -12,7 +12,6 @@ import edu.internet2.tier.shibboleth.admin.ui.service.ShibConfigurationService import edu.internet2.tier.shibboleth.admin.ui.util.WithMockAdmin import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.web.servlet.setup.MockMvcBuilders -import org.springframework.web.client.RestTemplate import spock.lang.Subject import javax.persistence.EntityManager @@ -46,7 +45,6 @@ class ShibPropertiesControllerTests extends AbstractBaseDataJpaTest { ShibConfigurationService shibConfigurationService def defaultSetResourceId - def mockRestTemplate = Mock(RestTemplate) def mockMvc @Transactional From 52f3349bd26e425d818e8149daf16badd3e7d956 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Thu, 25 May 2023 16:15:43 -0700 Subject: [PATCH 2/8] SHIBUI-2571 Intermediate work commit - feature incomplete --- .../admin/ui/beacon/BeaconDetail.java | 8 +-- .../admin/ui/beacon/ShibuiDetail.java | 26 ++++---- .../CoreShibUiConfiguration.java | 8 ++- .../configuration/auto/WebSecurityConfig.java | 2 + .../admin/ui/controller/BeaconController.java | 25 ++++++++ .../admin/ui/domain/BeaconConfiguration.java | 11 ++++ .../EntityDescriptorRepository.java | 2 + .../admin/ui/repository/FilterRepository.java | 6 +- .../MetadataResolverRepository.java | 6 +- .../ui/service/BeaconDataServiceImpl.java | 59 ++++++++++++++++--- delete.json | 16 +++++ .../net/unicon/shibui/pac4j/WebSecurity.java | 2 + 12 files changed, 140 insertions(+), 31 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/BeaconController.java create mode 100644 delete.json diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java index 24c9ccbc4..ac690b1cb 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java @@ -1,8 +1,10 @@ package edu.internet2.tier.shibboleth.admin.ui.beacon; import lombok.Data; +import lombok.experimental.Accessors; @Data +@Accessors(chain = true) public class BeaconDetail { private String msgType = "TIERBEACON"; private String msgName = "TIER"; @@ -14,8 +16,4 @@ public class BeaconDetail { private String tbMaintainer = "Unicon"; private ShibuiDetail shibui; -} - -// "tbProduct": "ShibUI (shibui.beacon.productName)", -// "tbProductVersion": "1.17.4 (can we get this from the app?)", -// "tbTIERRelease": "(if the TAP container: 1.17.4; if the Unicon package, INTERNAL_1.17.4)", \ No newline at end of file +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java index fdf0abf6b..ec89e5f00 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java @@ -1,21 +1,19 @@ package edu.internet2.tier.shibboleth.admin.ui.beacon; +import lombok.Builder; import lombok.Data; +import java.util.List; + @Data +@Builder public class ShibuiDetail { - - - // "shibui": { - // "authMechanisms": [ - // "pac4j-saml" - // ], - // "numberOfMetadataSources": 25, - // "numberOfMetadataProviders": 3, - // "numberOfFilters": 0, - // "dailyLogins": 12, - // "numberOfGroups": 15, - // "numberOfRoles": 5, - // "installationID": "4813c762-4a90-40a2-9c01-81bf67647da4" - // } + private List authMechanisms; + private int numberOfMetadataSources; + private int numberOfMetadataProviders; + private int numberOfFilters; + private int dailyLogins; + private int numberOfGroups; + private int numberOfRoles; + private String installationID; } \ 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 2012f9558..ce380f84c 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 @@ -5,6 +5,7 @@ import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects; import edu.internet2.tier.shibboleth.admin.ui.repository.BeaconConfigurationRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; +import edu.internet2.tier.shibboleth.admin.ui.repository.FilterRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolversPositionOrderContainerRepository; import edu.internet2.tier.shibboleth.admin.ui.scheduled.EntityDescriptorFilesScheduledTasks; @@ -270,9 +271,10 @@ public String getBeaconCronValue(BeaconConfigurationRepository repo) } @Bean - public IBeaconDataService getBeaconDataService(@Value("${shibui.beacon.productName:ShibUi}") String productName, InfoEndpoint info, @Value("#{environment.TIERVERSION}") String tierVersion) { -// productName = StringUtils.isBlank(productName) ? "ShibUI" : productName; - BeaconDataServiceImpl result = new BeaconDataServiceImpl(productName, info); + public IBeaconDataService getBeaconDataService(@Value("${shibui.beacon.productName:ShibUi}") String productName, InfoEndpoint info, @Value("#{environment.TIERVERSION}") String tierVersion, + EntityDescriptorRepository entityDescriptorRepository, MetadataResolverRepository metadataResolverRepository, FilterRepository filterRepository, + GroupsRepository groupsRepository, RoleRepository roleRepository, BeaconConfigurationRepository beaconConfigurationRepository, UserService userService) { + BeaconDataServiceImpl result = new BeaconDataServiceImpl(productName, info, tierVersion, entityDescriptorRepository, metadataResolverRepository, filterRepository, groupsRepository, roleRepository, beaconConfigurationRepository, userService); return result; } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java index 6e19098dd..719621596 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration.auto; +import edu.internet2.tier.shibboleth.admin.ui.domain.BeaconConfiguration; import edu.internet2.tier.shibboleth.admin.ui.security.DefaultAuditorAware; import edu.internet2.tier.shibboleth.admin.ui.security.model.Role; import edu.internet2.tier.shibboleth.admin.ui.security.model.User; @@ -80,6 +81,7 @@ public AuditorAware defaultAuditorAware() { @Bean @Profile("!no-auth") public WebSecurityConfigurerAdapter defaultAuth() { + BeaconConfiguration.setAuthMechanisms("default"); return new WebSecurityConfigurerAdapter() { @Override diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/BeaconController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/BeaconController.java new file mode 100644 index 000000000..f5ece2032 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/BeaconController.java @@ -0,0 +1,25 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import com.fasterxml.jackson.core.JsonProcessingException; +import edu.internet2.tier.shibboleth.admin.ui.service.IBeaconDataService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +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; + +@Profile({"dev", "very-dangerous"}) +@RestController +@RequestMapping(value = "/api/beacon") +public class BeaconController { + + @Autowired + private IBeaconDataService service; + + + @GetMapping(value = "/detail") + public ResponseEntity getDetail() throws JsonProcessingException { + return ResponseEntity.ok(service.getBeaconData()); + } +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BeaconConfiguration.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BeaconConfiguration.java index 93d76fdc3..c3b263c77 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BeaconConfiguration.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/BeaconConfiguration.java @@ -1,9 +1,11 @@ package edu.internet2.tier.shibboleth.admin.ui.domain; import lombok.Data; +import lombok.Setter; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.Transient; @Data @Entity @@ -15,6 +17,15 @@ public class BeaconConfiguration { private String sendCron; + // Comma separated list of the auth mechanisms used. + @Setter + @Transient + private static String authMechanisms = "unset"; + + public static String getAuthMechanisms() { + return authMechanisms; + } + /** * enforce that id will only ever be 1 */ 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 7fb999568..44ca96afe 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 @@ -60,4 +60,6 @@ public interface EntityDescriptorRepository extends JpaRepository getEntityDescriptorsNeedingApproval(@Param("groupIds") List groupIds); + @Query("SELECT COUNT(ed) FROM EntityDescriptor ed WHERE ed.serviceEnabled = true") + int getActiveEntityCount(); } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/FilterRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/FilterRepository.java index 8c2273d77..e36d55d3c 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/FilterRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/FilterRepository.java @@ -1,8 +1,12 @@ package edu.internet2.tier.shibboleth.admin.ui.repository; import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; public interface FilterRepository extends CrudRepository { MetadataFilter findByResourceId(String resourceId); -} + + @Query("SELECT COUNT(f) FROM MetadataFilter f WHERE f.filterEnabled = true") + int getActiveFilterCount(); +} \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepository.java index 07835ea9a..333fb2c11 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/MetadataResolverRepository.java @@ -1,6 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.repository; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; /** @@ -11,4 +12,7 @@ public interface MetadataResolverRepository extends CrudRepository emailService, Pac4jConfigurationProperties pac4jConfigurationProperties, IGroupService groupService) { + BeaconConfiguration.setAuthMechanisms("Pac4J - SAML2 provider"); return new Pac4jWebSecurityConfigurerAdapter(config, userService, rolesService, emailService, groupService, pac4jConfigurationProperties); } From 9af81914bdbc910d4ad92b2ad945a9b530aa5ef3 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Fri, 26 May 2023 12:35:15 -0700 Subject: [PATCH 3/8] SHIBUI-2571 Beacon gathering and sending --- .../CoreShibUiConfiguration.java | 14 +++++++++ .../admin/ui/controller/BeaconController.java | 11 ++++++- .../ui/scheduled/BeaconReportingTask.java | 30 +++++++++++++++++-- .../src/main/resources/application.properties | 1 + 4 files changed, 53 insertions(+), 3 deletions(-) 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 ce380f84c..6137f80cf 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 @@ -71,6 +71,11 @@ import javax.servlet.http.HttpServletRequest; import javax.sql.DataSource; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Optional; @Configuration @@ -277,4 +282,13 @@ public IBeaconDataService getBeaconDataService(@Value("${shibui.beacon.productNa BeaconDataServiceImpl result = new BeaconDataServiceImpl(productName, info, tierVersion, entityDescriptorRepository, metadataResolverRepository, filterRepository, groupsRepository, roleRepository, beaconConfigurationRepository, userService); return result; } + + @Bean + public List beaconEndpointUrl(@Value("${shibui.beacon.url}") String urls) throws MalformedURLException { + List result = new ArrayList<>(); + for (String url : Arrays.asList(urls.split(","))) { + result.add(new URL(url)); + } + return result; + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/BeaconController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/BeaconController.java index f5ece2032..e41f17f39 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/BeaconController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/BeaconController.java @@ -1,11 +1,13 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; import com.fasterxml.jackson.core.JsonProcessingException; +import edu.internet2.tier.shibboleth.admin.ui.scheduled.BeaconReportingTask; import edu.internet2.tier.shibboleth.admin.ui.service.IBeaconDataService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -13,13 +15,20 @@ @RestController @RequestMapping(value = "/api/beacon") public class BeaconController { + @Autowired + BeaconReportingTask beaconReporter; @Autowired private IBeaconDataService service; - @GetMapping(value = "/detail") public ResponseEntity getDetail() throws JsonProcessingException { return ResponseEntity.ok(service.getBeaconData()); } + + @PostMapping("/send") + public ResponseEntity forceSendBeaconData() { + beaconReporter.sendBeaconData(); + return ResponseEntity.ok("Manual push of beacon data completed"); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/BeaconReportingTask.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/BeaconReportingTask.java index c87d7017c..5c25c8603 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/BeaconReportingTask.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/scheduled/BeaconReportingTask.java @@ -4,11 +4,18 @@ import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; + @Configuration @ConditionalOnProperty(name = "shibui.beacon.enabled", matchIfMissing=true) @EnableSchedulerLock(defaultLockAtMostFor = "${shibui.maxTask.lockTime:30m}") @@ -16,10 +23,29 @@ public class BeaconReportingTask { @Autowired IBeaconDataService dataService; + @Autowired + @Qualifier("beaconEndpointUrl") + private List endpointUrls; + @Scheduled(cron="#{@getBeaconCronValue}") - @SchedulerLock(name = "generateEntityDescriptorFiles") + @SchedulerLock(name = "sendBeaconData") @Transactional(readOnly = true) - public void generateEntityDescriptorFiles() { + public void sendBeaconData() { + endpointUrls.forEach(url -> { + try { + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/json; utf-8"); + con.setRequestProperty("Accept", "application/json"); + con.setDoOutput(true); + try(OutputStream os = con.getOutputStream()){ + byte[] input = dataService.getBeaconData().getBytes("utf-8"); + os.write(input, 0, input.length); + } + } catch (IOException e) { + e.printStackTrace(); + } + }); } } \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 90472bf0e..61430612c 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -122,6 +122,7 @@ shibui.roles.authenticated=ADMIN,ENABLE,USER shibui.beacon.enabled=true shibui.beacon.productName=ShibUi shibui.beacon.installationID=UNICON-SHIBUI-TESTING +shibui.beacon.url=http://collector.testbed.tier.internet2.edu:5001 ### Swagger/Springdoc patterns springdoc.use-management-port=true From 2ba2ebbd964799444d5c8b6d999004d6cb438743 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 30 May 2023 11:19:08 -0700 Subject: [PATCH 4/8] SHIBUI-2571 Added database to output --- .../ui/configuration/CoreShibUiConfiguration.java | 6 ++++-- .../admin/ui/service/BeaconDataServiceImpl.java | 13 ++++++++++--- .../admin/ui/{ => service}/beacon/BeaconDetail.java | 2 +- .../admin/ui/{ => service}/beacon/ShibuiDetail.java | 3 ++- 4 files changed, 17 insertions(+), 7 deletions(-) rename backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/{ => service}/beacon/BeaconDetail.java (86%) rename backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/{ => service}/beacon/ShibuiDetail.java (82%) 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 6137f80cf..051f29cf0 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 @@ -51,6 +51,7 @@ 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.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.info.InfoEndpoint; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -278,8 +279,9 @@ public String getBeaconCronValue(BeaconConfigurationRepository repo) @Bean public IBeaconDataService getBeaconDataService(@Value("${shibui.beacon.productName:ShibUi}") String productName, InfoEndpoint info, @Value("#{environment.TIERVERSION}") String tierVersion, EntityDescriptorRepository entityDescriptorRepository, MetadataResolverRepository metadataResolverRepository, FilterRepository filterRepository, - GroupsRepository groupsRepository, RoleRepository roleRepository, BeaconConfigurationRepository beaconConfigurationRepository, UserService userService) { - BeaconDataServiceImpl result = new BeaconDataServiceImpl(productName, info, tierVersion, entityDescriptorRepository, metadataResolverRepository, filterRepository, groupsRepository, roleRepository, beaconConfigurationRepository, userService); + GroupsRepository groupsRepository, RoleRepository roleRepository, BeaconConfigurationRepository beaconConfigurationRepository, + UserService userService, HealthEndpoint healthEndpoint) { + BeaconDataServiceImpl result = new BeaconDataServiceImpl(productName, info, tierVersion, entityDescriptorRepository, metadataResolverRepository, filterRepository, groupsRepository, roleRepository, beaconConfigurationRepository, userService, healthEndpoint); return result; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java index f1b4a7e8a..0047275d7 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java @@ -2,8 +2,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; -import edu.internet2.tier.shibboleth.admin.ui.beacon.BeaconDetail; -import edu.internet2.tier.shibboleth.admin.ui.beacon.ShibuiDetail; +import edu.internet2.tier.shibboleth.admin.ui.service.beacon.BeaconDetail; +import edu.internet2.tier.shibboleth.admin.ui.service.beacon.ShibuiDetail; import edu.internet2.tier.shibboleth.admin.ui.domain.BeaconConfiguration; import edu.internet2.tier.shibboleth.admin.ui.repository.BeaconConfigurationRepository; import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository; @@ -14,9 +14,11 @@ import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService; import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.actuate.health.HealthEndpoint; import org.springframework.boot.actuate.info.InfoEndpoint; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; public class BeaconDataServiceImpl implements IBeaconDataService { @@ -24,6 +26,7 @@ public class BeaconDataServiceImpl implements IBeaconDataService { private String productName; private String tierVersion; private String version; + private String db; private EntityDescriptorRepository entityDescriptorRepository; private MetadataResolverRepository metadataResolverRepository; @@ -35,12 +38,15 @@ public class BeaconDataServiceImpl implements IBeaconDataService { public BeaconDataServiceImpl(String productName, InfoEndpoint info, String tierVersion, EntityDescriptorRepository entityDescriptorRepository, MetadataResolverRepository metadataResolverRepository, FilterRepository filterRepository, GroupsRepository groupsRepository, - RoleRepository roleRepository, BeaconConfigurationRepository beaconConfigurationRepository, UserService userService) { + RoleRepository roleRepository, BeaconConfigurationRepository beaconConfigurationRepository, UserService userService, + HealthEndpoint health) { mapper = new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // skip any null values this.productName = productName; this.version = info.info().get("build") == null ? "unknown" : ((Map)info.info().get("build")).get("version").toString(); + this.db = StringUtils.substringBetween(health.healthForPath("db").toString(), "database=", ","); + this.tierVersion = StringUtils.isBlank(tierVersion) ? "NA" : tierVersion; this.entityDescriptorRepository = entityDescriptorRepository; @@ -72,6 +78,7 @@ private ShibuiDetail getShibuiDetailData() { .dailyLogins(userService.getDailyLoginCount()) .numberOfGroups((int) groupsRepository.count()) .numberOfRoles((int) roleRepository.count()) + .db(this.db) .build(); return detail; } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/BeaconDetail.java similarity index 86% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/BeaconDetail.java index ac690b1cb..bef94540f 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/BeaconDetail.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/BeaconDetail.java @@ -1,4 +1,4 @@ -package edu.internet2.tier.shibboleth.admin.ui.beacon; +package edu.internet2.tier.shibboleth.admin.ui.service.beacon; import lombok.Data; import lombok.experimental.Accessors; diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/ShibuiDetail.java similarity index 82% rename from backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java rename to backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/ShibuiDetail.java index ec89e5f00..6bab7ef96 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/beacon/ShibuiDetail.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/ShibuiDetail.java @@ -1,4 +1,4 @@ -package edu.internet2.tier.shibboleth.admin.ui.beacon; +package edu.internet2.tier.shibboleth.admin.ui.service.beacon; import lombok.Builder; import lombok.Data; @@ -9,6 +9,7 @@ @Builder public class ShibuiDetail { private List authMechanisms; + private String db; private int numberOfMetadataSources; private int numberOfMetadataProviders; private int numberOfFilters; From a6ba064f65925697d72c55efe22a1e25d05ced6f Mon Sep 17 00:00:00 2001 From: chasegawa Date: Tue, 30 May 2023 14:35:31 -0700 Subject: [PATCH 5/8] SHIBUI-2571 Changed output for logins --- .../ui/security/model/UserLoginRecord.java | 8 +--- .../repository/UserLoginRecordRepository.java | 5 ++- .../ui/security/service/UserService.java | 22 ++++++---- .../ui/service/BeaconDataServiceImpl.java | 1 + .../admin/ui/service/beacon/ShibuiDetail.java | 1 + .../ui/configuration/TestConfiguration.groovy | 24 ++++++++++ .../security/service/UserServiceTests.groovy | 42 ++++++++++-------- delete.json | 16 ------- .../unicon/shibui/pac4j/AddNewUserFilter.java | 1 - .../ShibuiSAML2Authenticator.java | 1 + .../shibui/pac4j/AddNewUserFilterTests.groovy | 44 ------------------- testbed/authentication/shibui/application.yml | 2 +- 12 files changed, 71 insertions(+), 96 deletions(-) delete mode 100644 delete.json diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserLoginRecord.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserLoginRecord.java index 75e5fa728..7ed12a93e 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserLoginRecord.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/model/UserLoginRecord.java @@ -11,7 +11,6 @@ @Data @Entity -@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "username", "login" }) }) public class UserLoginRecord { @Id @GeneratedValue @@ -19,16 +18,13 @@ public class UserLoginRecord { private String username; - private String login; - private Date loginDate; public UserLoginRecord() { } - public UserLoginRecord(String username, Date loginDate, String formattedDate) { + public UserLoginRecord(String username) { this.username = username; - this.login = formattedDate; - this.loginDate = loginDate; + this.loginDate = new Date(); } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserLoginRecordRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserLoginRecordRepository.java index 22a97fabb..db583b786 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserLoginRecordRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/repository/UserLoginRecordRepository.java @@ -9,8 +9,11 @@ import java.util.Optional; public interface UserLoginRecordRepository extends JpaRepository { - Optional findByUsernameAndLogin(String username, String formattedDate); + Optional findTopByUsername(String username); @Query(value = "SELECT count(*) FROM UserLoginRecord ulr WHERE ulr.loginDate >= :sinceDate") int countLoginsSince(@Param("sinceDate") Date sinceDate); + + @Query(value = "SELECT count(DISTINCT ulr.username) FROM UserLoginRecord ulr WHERE ulr.loginDate >= :sinceDate") + int countUniqueUserLoginsSince(@Param("sinceDate") Date sinceDate); } \ 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 e2a45b12f..735ce0614 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 @@ -251,21 +251,27 @@ public boolean currentUserCanEnable() { } /** - * Ensure there exists a login record for this username and today's date + * Ensure there exists a login record for this username and current time * @param username */ public void updateLoginRecord(String username) { - Date current = new Date(); - String formattedDate = DATE_FORMAT.format(current); - - if (userLoginRecordRepository.findByUsernameAndLogin(username,formattedDate).isEmpty()) { - UserLoginRecord ulr = new UserLoginRecord(username, current, formattedDate); - userLoginRecordRepository.saveAndFlush(ulr); - } + UserLoginRecord ulr = new UserLoginRecord(username); + userLoginRecordRepository.saveAndFlush(ulr); } + /** + * @return count of all logins in the last 24 hours + */ public int getDailyLoginCount() { Date since = DateUtils.addDays(new Date(), -1); return userLoginRecordRepository.countLoginsSince(since); } + + /** + * @return count of unique users logged in during the last 24 hours + */ + public int getDailyUniqueUserLogins() { + Date since = DateUtils.addDays(new Date(), -1); + return userLoginRecordRepository.countUniqueUserLoginsSince(since); + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java index 0047275d7..97cb5f06d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/BeaconDataServiceImpl.java @@ -76,6 +76,7 @@ private ShibuiDetail getShibuiDetailData() { .numberOfMetadataProviders(metadataResolverRepository.getActiveMetadataProviderCount()) .numberOfFilters(filterRepository.getActiveFilterCount()) .dailyLogins(userService.getDailyLoginCount()) + .dailyUniqueUserLogins(userService.getDailyUniqueUserLogins()) .numberOfGroups((int) groupsRepository.count()) .numberOfRoles((int) roleRepository.count()) .db(this.db) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/ShibuiDetail.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/ShibuiDetail.java index 6bab7ef96..8d3b01330 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/ShibuiDetail.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/service/beacon/ShibuiDetail.java @@ -14,6 +14,7 @@ public class ShibuiDetail { private int numberOfMetadataProviders; private int numberOfFilters; private int dailyLogins; + private int dailyUniqueUserLogins; private int numberOfGroups; private int numberOfRoles; private String installationID; 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 9b55239ca..3ad4c7969 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,14 @@ 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.actuate.endpoint.annotation.Selector +import org.springframework.boot.actuate.health.DefaultHealthContributorRegistry +import org.springframework.boot.actuate.health.Health +import org.springframework.boot.actuate.health.HealthComponent +import org.springframework.boot.actuate.health.HealthEndpoint +import org.springframework.boot.actuate.health.HealthEndpointGroup +import org.springframework.boot.actuate.health.HealthEndpointGroups +import org.springframework.boot.actuate.health.Status import org.springframework.boot.actuate.info.InfoContributor import org.springframework.boot.actuate.info.InfoEndpoint import org.springframework.boot.web.client.RestTemplateBuilder @@ -142,4 +150,20 @@ class TestConfiguration { public InfoEndpoint getInfoEndpoint() { return new InfoEndpoint(new ArrayList()); } + + @Bean + public HealthEndpoint getHealthEndpoint() { + return new HealthEndpoint(new DefaultHealthContributorRegistry(), new HealthEndpointGroups() { + @Override HealthEndpointGroup getPrimary() { return null } + + @Override Set getNames() { return null } + + @Override HealthEndpointGroup get(String name) { return null } + }) { + @Override + HealthComponent healthForPath(@Selector(match = Selector.Match.ALL_REMAINING) String... path) { + return new Health(new Status(""), new HashMap()); + } + } + } } \ No newline at end of file diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/UserServiceTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/UserServiceTests.groovy index 5b7b9feb0..df02e801c 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/UserServiceTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/security/service/UserServiceTests.groovy @@ -171,47 +171,51 @@ class UserServiceTests extends AbstractBaseDataJpaTest { def "when user login - ensure record is created"() { given: - SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy") - - Date current = new Date(); - String formattedDate = DATE_FORMAT.format(current) - UserLoginRecord ulr = new UserLoginRecord("username", current, formattedDate) + def sinceDate = DateUtils.addDays(new Date(), -1); + UserLoginRecord ulr = new UserLoginRecord("username") expect: - userLoginRecordRepository.findByUsernameAndLogin("username", formattedDate).isEmpty() + userLoginRecordRepository.findTopByUsername("username").isEmpty() userLoginRecordRepository.count() == 0 when: userService.updateLoginRecord("username") then: - userLoginRecordRepository.findByUsernameAndLogin("username", formattedDate).isPresent() + userLoginRecordRepository.findTopByUsername("username").isPresent() userLoginRecordRepository.count() == 1 + userLoginRecordRepository.countLoginsSince(sinceDate) == 1 + userLoginRecordRepository.countUniqueUserLoginsSince(sinceDate) == 1 - when: 'try adding again should not change result' + when: 'repeat login change results appropriately' userService.updateLoginRecord("username") then: - userLoginRecordRepository.findByUsernameAndLogin("username", formattedDate).isPresent() - userLoginRecordRepository.count() == 1 - userService.getDailyLoginCount() == 1 + userLoginRecordRepository.findTopByUsername("username").isPresent() + userLoginRecordRepository.count() == 2 + userLoginRecordRepository.countLoginsSince(sinceDate) == 2 + userLoginRecordRepository.countUniqueUserLoginsSince(sinceDate) == 1 - when: + when: 'new login' userService.updateLoginRecord("username2") then: - userLoginRecordRepository.findByUsernameAndLogin("username2", formattedDate).isPresent() - userLoginRecordRepository.count() == 2 - userService.getDailyLoginCount() == 2 + userLoginRecordRepository.findTopByUsername("username2").isPresent() + userLoginRecordRepository.count() == 3 + userLoginRecordRepository.countLoginsSince(sinceDate) == 3 + userLoginRecordRepository.countUniqueUserLoginsSince(sinceDate) == 2 when: 'older logins in db, should be same counts' Date older = DateUtils.addDays(new Date(), -3) - String formattedDate2 = DATE_FORMAT.format(older) - UserLoginRecord ulr2 = new UserLoginRecord("username", older, formattedDate2) + UserLoginRecord ulr2 = new UserLoginRecord("username").with { + it.loginDate = older + it + } userLoginRecordRepository.save(ulr2) then: - userLoginRecordRepository.count() == 3 - userService.getDailyLoginCount() == 2 + userLoginRecordRepository.count() == 4 + userLoginRecordRepository.countLoginsSince(sinceDate) == 3 + userLoginRecordRepository.countUniqueUserLoginsSince(sinceDate) == 2 } } \ No newline at end of file diff --git a/delete.json b/delete.json deleted file mode 100644 index 93b3d2534..000000000 --- a/delete.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "git": { - "branch": "feature/shibui-2571", - "commit": { - "id": "b547925", - "time": "2023-05-23T20:32:38Z" - } - }, - "build": { - "artifact": "shibui", - "name": "backend", - "time": "2023-05-24T17:15:28.585Z", - "version": "1.18.0-SNAPSHOT", - "group": "edu.internet2.tier.shibboleth.admin.ui" - } -} \ No newline at end of file diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java index 22dfe2f40..28565e7fc 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/AddNewUserFilter.java @@ -136,7 +136,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha ((HttpServletResponse) response).sendRedirect("/unsecured/error.html"); } else { // User exists or has been created and has a role so we can continue on. - userService.updateLoginRecord(username); chain.doFilter(request, response); // else, user is in the system already, carry on } } diff --git a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/authenticator/ShibuiSAML2Authenticator.java b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/authenticator/ShibuiSAML2Authenticator.java index f2d2738e0..110611f2d 100644 --- a/pac4j-module/src/main/java/net/unicon/shibui/pac4j/authenticator/ShibuiSAML2Authenticator.java +++ b/pac4j-module/src/main/java/net/unicon/shibui/pac4j/authenticator/ShibuiSAML2Authenticator.java @@ -28,5 +28,6 @@ public void validate(final Credentials credentials, final WebContext context, fi CommonProfile profile = (CommonProfile) credentials.getUserProfile(); profile.setRoles(userService.getUserRoles(profile.getUsername())); credentials.setUserProfile(profile); + userService.updateLoginRecord(profile.getUsername()); } } \ No newline at end of file diff --git a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy index 2097ccaa1..216ccdafe 100644 --- a/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy +++ b/pac4j-module/src/test/groovy/net/unicon/shibui/pac4j/AddNewUserFilterTests.groovy @@ -1,11 +1,9 @@ package net.unicon.shibui.pac4j -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.OwnershipRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository -import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserLoginRecordRepository import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository import edu.internet2.tier.shibboleth.admin.ui.security.service.GroupServiceForTesting import edu.internet2.tier.shibboleth.admin.ui.security.service.IRolesService @@ -30,7 +28,6 @@ import spock.lang.Subject import javax.servlet.FilterChain import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletResponse -import java.text.SimpleDateFormat @DataJpaTest @ContextConfiguration(classes=[Pac4JTestingConfig]) @@ -57,9 +54,6 @@ class AddNewUserFilterTests extends Specification { @Autowired Pac4jConfigurationProperties pac4jConfigurationProperties - @Autowired - UserLoginRecordRepository userLoginRecordRepository - @Autowired UserRepository userRepository @@ -107,44 +101,6 @@ class AddNewUserFilterTests extends Specification { roles.each { roleRepository.save(it) } - - Group gb = new Group() - gb.setResourceId("testingGroupBBB") - gb.setName("Group BBB") - gb.setValidationRegex("^(?:https?:\\/\\/)?(?:[^.]+\\.)?shib\\.org(\\/.*)?\$") - 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) - } - - def "user logged in"() { - given: - ['Username': 'someUser', - 'FirstName': 'Some', - 'LastName': 'User', - 'Email': 'someUser@institution.edu'].each { key, value -> - saml2Profile.getAttribute(profileMapping."get${key}"()) >> [value] - } - saml2Profile.getUsername() >> "someUser" - - SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy") - - Date current = new Date(); - String formattedDate = DATE_FORMAT.format(current) - - expect: - userLoginRecordRepository.findByUsernameAndLogin("someUser", formattedDate).isEmpty() - userLoginRecordRepository.count() == 0 - - when: - addNewUserFilter.doFilter(request, response, chain) - - then: - userLoginRecordRepository.findByUsernameAndLogin("someUser", formattedDate).isPresent() - userLoginRecordRepository.count() == 1 } def "new user created"() { diff --git a/testbed/authentication/shibui/application.yml b/testbed/authentication/shibui/application.yml index 942d3aaaf..fb64e02d0 100644 --- a/testbed/authentication/shibui/application.yml +++ b/testbed/authentication/shibui/application.yml @@ -3,7 +3,7 @@ server: forward-headers-strategy: NATIVE spring: profiles: - include: + include: dev shibui: user-bootstrap-resource: file:/conf/users.csv roles: ROLE_ADMIN,ROLE_NONE,ROLE_USER,ROLE_ENABLE,ROLE_PONY From 777fc5ec19e6a8b11d54e03e1300dffce30c88e7 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 31 May 2023 14:29:32 -0700 Subject: [PATCH 6/8] SHIBUI-2578/2579 Fixes for issues found by QA --- .../configuration/auto/WebSecurityConfig.java | 23 +++++++++++++++++++ .../springsecurity/AdminUserService.java | 2 -- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java index 719621596..1bcc57f49 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java @@ -15,10 +15,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.AuditorAware; +import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.factory.PasswordEncoderFactories; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; @@ -132,6 +135,7 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { .roles("ADMIN"); } auth.userDetailsService(adminUserService(userService)).passwordEncoder(passwordEncoder); + auth.authenticationEventPublisher(new RecordLoginHandler(userService)); } @Override @@ -165,4 +169,23 @@ public void configure(WebSecurity web) throws Exception { } }; } + + class RecordLoginHandler implements AuthenticationEventPublisher { + private UserService userService; + + public RecordLoginHandler(UserService userService) { + this.userService = userService; + } + + @Override + public void publishAuthenticationSuccess(Authentication authentication) { + // do this in SimpleAuthenticationProvider in v2.0 + userService.updateLoginRecord(authentication.getName()); + } + + @Override + public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) { + // don't care about this + } + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserService.java index f8a128a9b..7c2372227 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/springsecurity/AdminUserService.java @@ -26,7 +26,6 @@ public class AdminUserService implements UserDetailsService { private final UserService userService; @Override - @Transactional(readOnly = true) public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService .findByUsername(username) @@ -42,7 +41,6 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx throw new UsernameNotFoundException(String.format("No roles are defined for user [%s]", username)); } - userService.updateLoginRecord(username); return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); } } \ No newline at end of file From 05e68110adacdf227fba9bd2577120faf0d4b132 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 31 May 2023 15:59:13 -0700 Subject: [PATCH 7/8] SHIBUI-2578/2579 Removed old beacon code that is unused --- backend/build.gradle | 3 - beacon/core/build.gradle | 37 -------- .../java/edu/internet2/tap/beacon/Beacon.java | 22 ----- .../internet2/tap/beacon/BeaconPublisher.java | 10 --- .../tap/beacon/DefaultBeaconPublisher.java | 90 ------------------- .../beacon/DefaultBeaconPublisherTests.groovy | 45 ---------- beacon/gradle.properties | 2 - beacon/spring/build.gradle | 34 ------- .../BeaconPublishingConfiguration.java | 61 ------------- .../BeaconEnvironmentVariablesCondition.java | 48 ---------- ...alOnBeaconEnvironmentVariablesPresent.java | 21 ----- .../main/resources/META-INF/spring.factories | 1 - 12 files changed, 374 deletions(-) delete mode 100644 beacon/core/build.gradle delete mode 100644 beacon/core/src/main/java/edu/internet2/tap/beacon/Beacon.java delete mode 100644 beacon/core/src/main/java/edu/internet2/tap/beacon/BeaconPublisher.java delete mode 100644 beacon/core/src/main/java/edu/internet2/tap/beacon/DefaultBeaconPublisher.java delete mode 100644 beacon/core/src/test/groovy/edu/internet2/tap/beacon/DefaultBeaconPublisherTests.groovy delete mode 100644 beacon/gradle.properties delete mode 100644 beacon/spring/build.gradle delete mode 100644 beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/BeaconPublishingConfiguration.java delete mode 100644 beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/condition/BeaconEnvironmentVariablesCondition.java delete mode 100644 beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/condition/ConditionalOnBeaconEnvironmentVariablesPresent.java delete mode 100644 beacon/spring/src/main/resources/META-INF/spring.factories diff --git a/backend/build.gradle b/backend/build.gradle index bcc5df148..d6352b9f4 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -245,9 +245,6 @@ dependencies { //Pacj4 sub-project runtimeOnly project(':pac4j-module') - //Beacon - runtimeOnly project(':beacon:spring') - enversTestCompile sourceSets.main.output enversTestCompile sourceSets.test.output enversTestCompile configurations.compile diff --git a/beacon/core/build.gradle b/beacon/core/build.gradle deleted file mode 100644 index b4ddfd4ca..000000000 --- a/beacon/core/build.gradle +++ /dev/null @@ -1,37 +0,0 @@ -import org.springframework.boot.gradle.plugin.SpringBootPlugin - -plugins { - id 'org.springframework.boot' - id 'io.spring.dependency-management' version '1.0.6.RELEASE' - id 'groovy' -} - -sourceCompatibility = 11 -targetCompatibility = 11 - -bootJar.enabled = false - -repositories { - jcenter() - maven { // for the springboot plugin - url "https://plugins.gradle.org/m2/" - } -} - -dependencyManagement { - imports { - mavenBom SpringBootPlugin.BOM_COORDINATES - } -} - -dependencies { - testCompile "org.springframework.boot:spring-boot-starter-test:${project.'springbootVersion'}" - compile 'org.codehaus.groovy:groovy-all:3.0.10' - testImplementation platform("org.spockframework:spock-bom:2.1-groovy-3.0") - testImplementation "org.spockframework:spock-core" - testImplementation "org.spockframework:spock-spring" -} - -jar { - archiveName = "beacon-core-${version}.jar" -} \ No newline at end of file diff --git a/beacon/core/src/main/java/edu/internet2/tap/beacon/Beacon.java b/beacon/core/src/main/java/edu/internet2/tap/beacon/Beacon.java deleted file mode 100644 index 7e7dec5a5..000000000 --- a/beacon/core/src/main/java/edu/internet2/tap/beacon/Beacon.java +++ /dev/null @@ -1,22 +0,0 @@ -package edu.internet2.tap.beacon; - -/** - * Exposes expected names of environment variables holding beacon config data. - */ -public final class Beacon { - - private Beacon() { - } - - public static final String LOG_HOST = "LOGHOST"; - - public static final String LOG_PORT = "LOGPORT"; - - public static final String IMAGE = "IMAGE"; - - public static final String VERSION = "VERSION"; - - public static final String TIERVERSION = "TIERVERSION"; - - public static final String MAINTAINER = "MAINTAINER"; -} diff --git a/beacon/core/src/main/java/edu/internet2/tap/beacon/BeaconPublisher.java b/beacon/core/src/main/java/edu/internet2/tap/beacon/BeaconPublisher.java deleted file mode 100644 index 80f4f77e4..000000000 --- a/beacon/core/src/main/java/edu/internet2/tap/beacon/BeaconPublisher.java +++ /dev/null @@ -1,10 +0,0 @@ -package edu.internet2.tap.beacon; - -/** - * Simple SPI allowing implementations to publish to beacon service utilizing Runnable API - * so that publishing code could run in separate threads of execution. - * - * @author Dmitriy Kopylenko - */ -public interface BeaconPublisher extends Runnable { -} diff --git a/beacon/core/src/main/java/edu/internet2/tap/beacon/DefaultBeaconPublisher.java b/beacon/core/src/main/java/edu/internet2/tap/beacon/DefaultBeaconPublisher.java deleted file mode 100644 index eb077ddc9..000000000 --- a/beacon/core/src/main/java/edu/internet2/tap/beacon/DefaultBeaconPublisher.java +++ /dev/null @@ -1,90 +0,0 @@ -package edu.internet2.tap.beacon; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import static edu.internet2.tap.beacon.Beacon.IMAGE; -import static edu.internet2.tap.beacon.Beacon.LOG_HOST; -import static edu.internet2.tap.beacon.Beacon.LOG_PORT; -import static edu.internet2.tap.beacon.Beacon.MAINTAINER; -import static edu.internet2.tap.beacon.Beacon.TIERVERSION; -import static edu.internet2.tap.beacon.Beacon.VERSION; - -/** - * Default implementation that knows the details about payload structure with its data and beacon endpoint details - * gathered by upstream components and passed to this implementation at object construction site. - * - * @author Dmitriy Kopylenko - */ -public class DefaultBeaconPublisher implements BeaconPublisher { - - private final URL endpointUrl; - - private final String jsonPayload; - - public DefaultBeaconPublisher(Map beaconDetails) { - //Do data validation checks here. If any of the necessary beacon data not available here, throw a Runtime exception - if (beaconDetails == null) { - throw new IllegalArgumentException("beaconDetails Map must not be null"); - } - if (beaconDetails.get(LOG_HOST) == null - || beaconDetails.get(LOG_PORT) == null - || beaconDetails.get(IMAGE) == null - || beaconDetails.get(VERSION) == null - || beaconDetails.get(TIERVERSION) == null - || beaconDetails.get(MAINTAINER) == null) { - throw new IllegalArgumentException("Not all the necessary beacon data is available to be able to publish to beacon"); - } - try { - this.endpointUrl = new URL(String.format("http://%s:%s", beaconDetails.get(LOG_HOST), beaconDetails.get(LOG_PORT))); - } catch (MalformedURLException ex) { - throw new IllegalArgumentException(ex.getMessage()); - } - - Map dataToPublish = new HashMap<>(); - dataToPublish.put("msgType", "TIERBEACON"); - dataToPublish.put("msgName", "TIER"); - dataToPublish.put("msgVersion", "1.0"); - dataToPublish.put("tbProduct", beaconDetails.get(IMAGE)); - dataToPublish.put("tbProductVersion", beaconDetails.get(VERSION)); - dataToPublish.put("tbTIERRelease", beaconDetails.get(TIERVERSION)); - dataToPublish.put("tbMaintainer", beaconDetails.get(MAINTAINER)); - - //Create JSON payload without any 3-rd party library - this.jsonPayload = "{" + dataToPublish.entrySet().stream() - .map(e -> "\"" + e.getKey() + "\"" + ":\"" + e.getValue() + "\"") - .collect(Collectors.joining(", ")) + "}"; - } - - @Override - public void run() { - try { - HttpURLConnection con = (HttpURLConnection) this.endpointUrl.openConnection(); - con.setRequestMethod("POST"); - con.setRequestProperty("Content-Type", "application/json; utf-8"); - con.setRequestProperty("Accept", "application/json"); - con.setDoOutput(true); - try(OutputStream os = con.getOutputStream()){ - byte[] input = jsonPayload.getBytes("utf-8"); - os.write(input, 0, input.length); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - //getters used in unit tests and calling components for debugging purposes - public String getEndpointUri() { - return endpointUrl.toString(); - } - - public String getJsonPayload() { - return jsonPayload; - } -} diff --git a/beacon/core/src/test/groovy/edu/internet2/tap/beacon/DefaultBeaconPublisherTests.groovy b/beacon/core/src/test/groovy/edu/internet2/tap/beacon/DefaultBeaconPublisherTests.groovy deleted file mode 100644 index d3004ef13..000000000 --- a/beacon/core/src/test/groovy/edu/internet2/tap/beacon/DefaultBeaconPublisherTests.groovy +++ /dev/null @@ -1,45 +0,0 @@ -package edu.internet2.tap.beacon - -import spock.lang.Specification -import sun.security.x509.OtherName - -class DefaultBeaconPublisherTests extends Specification { - - def "DefaultBeaconPublisher invariants are enforced during object creation - null Map is passed"() { - when: - new DefaultBeaconPublisher(null) - - then: - thrown IllegalArgumentException - - } - - def "DefaultBeaconPublisher invariants are enforced during object creation - empty Map is passed"() { - when: - new DefaultBeaconPublisher([:]) - - then: - thrown IllegalArgumentException - } - - def "DefaultBeaconPublisher invariants are enforced during object creation - valid Beacon data Map is passed"() { - when: - def expectedJsonPaylaod = """{"msgType":"TIERBEACON", "tbMaintainer":"unittest_maintainer", "msgName":"TIER", "tbProduct":"image", "msgVersion":"1.0", "tbProductVersion":"v1", "tbTIERRelease":"tv1"}""" - - def configuredBeaconData = [LOGHOST : 'collector.testbed.tier.internet2.edu', - LOGPORT : '5001', - IMAGE : 'image', - VERSION : 'v1', - TIERVERSION: 'tv1', - MAINTAINER : 'unittest_maintainer'] - def p = new DefaultBeaconPublisher(configuredBeaconData) - println p.jsonPayload - - then: - noExceptionThrown() - p.endpointUri == 'http://collector.testbed.tier.internet2.edu:5001' - p.jsonPayload == expectedJsonPaylaod - - } - -} diff --git a/beacon/gradle.properties b/beacon/gradle.properties deleted file mode 100644 index 23dbf7481..000000000 --- a/beacon/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -group=edu.internet2.tap.beacon -version=1.0.0-SNAPSHOT diff --git a/beacon/spring/build.gradle b/beacon/spring/build.gradle deleted file mode 100644 index eb6d1e991..000000000 --- a/beacon/spring/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -import org.springframework.boot.gradle.plugin.SpringBootPlugin - -plugins { - id 'org.springframework.boot' - id 'io.spring.dependency-management' version '1.0.6.RELEASE' -} - -apply plugin: 'java' -sourceCompatibility = 11 -targetCompatibility = 11 - -bootJar.enabled = false - -repositories { - jcenter() - maven { // for the springboot plugin - url "https://plugins.gradle.org/m2/" - } -} - -jar { - archiveName = "beacon-spring-${version}.jar" -} - -dependencyManagement { - imports { - mavenBom SpringBootPlugin.BOM_COORDINATES - } -} - -dependencies { - compile project(':beacon:core') - compile "org.springframework.boot:spring-boot-starter" -} \ No newline at end of file diff --git a/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/BeaconPublishingConfiguration.java b/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/BeaconPublishingConfiguration.java deleted file mode 100644 index f0324bacd..000000000 --- a/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/BeaconPublishingConfiguration.java +++ /dev/null @@ -1,61 +0,0 @@ -package edu.internet2.tap.beacon.configuration; - -import edu.internet2.tap.beacon.DefaultBeaconPublisher; -import edu.internet2.tap.beacon.configuration.condition.ConditionalOnBeaconEnvironmentVariablesPresent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; -import org.springframework.scheduling.annotation.Async; -import org.springframework.scheduling.annotation.Scheduled; - -import java.util.HashMap; -import java.util.Map; - -import static edu.internet2.tap.beacon.Beacon.IMAGE; -import static edu.internet2.tap.beacon.Beacon.LOG_HOST; -import static edu.internet2.tap.beacon.Beacon.LOG_PORT; -import static edu.internet2.tap.beacon.Beacon.MAINTAINER; -import static edu.internet2.tap.beacon.Beacon.TIERVERSION; -import static edu.internet2.tap.beacon.Beacon.VERSION; - -@Configuration -@ConditionalOnProperty(name = "shibui.beacon-enabled", havingValue = "true") -public class BeaconPublishingConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(BeaconPublishingConfiguration.class); - - @Bean - @ConditionalOnBeaconEnvironmentVariablesPresent - public BeaconPublishingTask beaconPublisher(Environment env) { - logger.debug("Creating BeaconPublishingTask..."); - Map beaconData = new HashMap<>(); - beaconData.put(LOG_HOST, env.getProperty(LOG_HOST)); - beaconData.put(LOG_PORT, env.getProperty(LOG_PORT)); - beaconData.put(IMAGE, env.getProperty(IMAGE)); - beaconData.put(VERSION, env.getProperty(VERSION)); - beaconData.put(TIERVERSION, env.getProperty(TIERVERSION)); - beaconData.put(MAINTAINER, env.getProperty(MAINTAINER)); - return new BeaconPublishingTask(new DefaultBeaconPublisher(beaconData)); - } - - public static class BeaconPublishingTask { - private DefaultBeaconPublisher beaconPublisher; - - public BeaconPublishingTask(DefaultBeaconPublisher beaconPublisher) { - this.beaconPublisher = beaconPublisher; - } - - //Cron is based on the spec defined here: https://spaces.at.internet2.edu/display/TWGH/TIER+Instrumentation+-+The+TIER+Beacon - @Scheduled(cron = "0 ${random.int[0,59]} ${random.int[0,3]} ? * *") - @Async - void publish() { - logger.debug("Publishing payload: {} to beacon endpoint: {}", - beaconPublisher.getJsonPayload(), - beaconPublisher.getEndpointUri()); - beaconPublisher.run(); - } - } -} diff --git a/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/condition/BeaconEnvironmentVariablesCondition.java b/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/condition/BeaconEnvironmentVariablesCondition.java deleted file mode 100644 index 84f8857fc..000000000 --- a/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/condition/BeaconEnvironmentVariablesCondition.java +++ /dev/null @@ -1,48 +0,0 @@ -package edu.internet2.tap.beacon.configuration.condition; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.env.Environment; -import org.springframework.core.type.AnnotatedTypeMetadata; - -import static edu.internet2.tap.beacon.Beacon.IMAGE; -import static edu.internet2.tap.beacon.Beacon.LOG_HOST; -import static edu.internet2.tap.beacon.Beacon.LOG_PORT; -import static edu.internet2.tap.beacon.Beacon.MAINTAINER; -import static edu.internet2.tap.beacon.Beacon.TIERVERSION; -import static edu.internet2.tap.beacon.Beacon.VERSION; - -/** - * {@link Condition} that checks for required beacon environment variables. - * - * @author Dmitriy Kopylenko - * @see ConditionalOnBeaconEnvironmentVariablesPresent - */ -public class BeaconEnvironmentVariablesCondition extends SpringBootCondition { - - private static final String MATCHED_MSG = "Beacon properties are present. Beacon activation condition is matched."; - - private static final String NOT_MATCHED_MSG = "Beacon properties are not present. Beacon activation condition is not matched."; - - private static final Logger logger = LoggerFactory.getLogger(BeaconEnvironmentVariablesCondition.class); - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - Environment env = context.getEnvironment(); - if (env.containsProperty(LOG_HOST) - && env.containsProperty(LOG_PORT) - && env.containsProperty(IMAGE) - && env.containsProperty(VERSION) - && env.containsProperty(TIERVERSION) - && env.containsProperty(MAINTAINER)) { - logger.debug(MATCHED_MSG); - return ConditionOutcome.match(MATCHED_MSG); - } - logger.debug(NOT_MATCHED_MSG); - return ConditionOutcome.noMatch(NOT_MATCHED_MSG); - } -} diff --git a/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/condition/ConditionalOnBeaconEnvironmentVariablesPresent.java b/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/condition/ConditionalOnBeaconEnvironmentVariablesPresent.java deleted file mode 100644 index 66049923b..000000000 --- a/beacon/spring/src/main/java/edu/internet2/tap/beacon/configuration/condition/ConditionalOnBeaconEnvironmentVariablesPresent.java +++ /dev/null @@ -1,21 +0,0 @@ -package edu.internet2.tap.beacon.configuration.condition; - -import org.springframework.context.annotation.Conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * {@link Conditional} that matches specific beacon environment variables are all present. - * - * @author Dmitriy Kopylenko - */ -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Conditional(BeaconEnvironmentVariablesCondition.class) -public @interface ConditionalOnBeaconEnvironmentVariablesPresent { -} diff --git a/beacon/spring/src/main/resources/META-INF/spring.factories b/beacon/spring/src/main/resources/META-INF/spring.factories deleted file mode 100644 index ae9c29c00..000000000 --- a/beacon/spring/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=edu.internet2.tap.beacon.configuration.BeaconPublishingConfiguration \ No newline at end of file From b68408b07d890445e69c16b9b640ac3173aec842 Mon Sep 17 00:00:00 2001 From: chasegawa Date: Wed, 31 May 2023 16:00:04 -0700 Subject: [PATCH 8/8] SHIBUI-2578/2579 Slight change to handling the auth event --- .../CoreShibUiConfiguration.java | 25 +++++++++++++++++++ .../configuration/auto/WebSecurityConfig.java | 20 --------------- 2 files changed, 25 insertions(+), 20 deletions(-) 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 051f29cf0..88607722e 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 @@ -56,6 +56,7 @@ 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.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -63,6 +64,10 @@ import org.springframework.context.support.ResourceBundleMessageSource; import org.springframework.core.io.Resource; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.authentication.AuthenticationEventPublisher; +import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; @@ -293,4 +298,24 @@ public List beaconEndpointUrl(@Value("${shibui.beacon.url}") String urls) t } return result; } + + @Bean + public AuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher, UserService userService) { + return new RecordLoginHandler(applicationEventPublisher, userService); + } + + class RecordLoginHandler extends DefaultAuthenticationEventPublisher { + private UserService userService; + + public RecordLoginHandler(ApplicationEventPublisher applicationEventPublisher, UserService userService) { + super(applicationEventPublisher); + this.userService = userService; + } + + @Override + public void publishAuthenticationSuccess(Authentication authentication) { + super.publishAuthenticationSuccess(authentication); + userService.updateLoginRecord(authentication.getName()); + } + } } \ No newline at end of file diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java index 1bcc57f49..2b44f5b01 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/configuration/auto/WebSecurityConfig.java @@ -135,7 +135,6 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception { .roles("ADMIN"); } auth.userDetailsService(adminUserService(userService)).passwordEncoder(passwordEncoder); - auth.authenticationEventPublisher(new RecordLoginHandler(userService)); } @Override @@ -169,23 +168,4 @@ public void configure(WebSecurity web) throws Exception { } }; } - - class RecordLoginHandler implements AuthenticationEventPublisher { - private UserService userService; - - public RecordLoginHandler(UserService userService) { - this.userService = userService; - } - - @Override - public void publishAuthenticationSuccess(Authentication authentication) { - // do this in SimpleAuthenticationProvider in v2.0 - userService.updateLoginRecord(authentication.getName()); - } - - @Override - public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) { - // don't care about this - } - } } \ No newline at end of file