Skip to content

Commit

Permalink
SHIBUI-2571
Browse files Browse the repository at this point in the history
Intermediate work commit - feature incomplete
  • Loading branch information
chasegawa committed Jun 2, 2023
1 parent 6914a08 commit a9c8bca
Show file tree
Hide file tree
Showing 15 changed files with 298 additions and 5 deletions.
14 changes: 14 additions & 0 deletions backend/src/main/app-resources/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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)",
Original file line number Diff line number Diff line change
@@ -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"
// }
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -42,10 +46,12 @@
import jakarta.servlet.http.HttpServletRequest;
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.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand All @@ -64,6 +70,7 @@
import org.springframework.web.util.UrlPathHelper;

import javax.sql.DataSource;
import java.util.Optional;

@SpringBootConfiguration
@Import(SearchConfiguration.class)
Expand Down Expand Up @@ -254,4 +261,17 @@ 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<BeaconConfiguration> 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) {
BeaconDataServiceImpl result = new BeaconDataServiceImpl(productName, info);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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;
Expand All @@ -18,6 +19,7 @@

@Controller
public class RootUiViewController {
@Autowired HealthEndpoint healthEndpoint;

@Autowired InfoEndpoint infoEndpoint;

Expand Down Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<BeaconConfiguration, Integer> {

}
Original file line number Diff line number Diff line change
@@ -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() {

}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
6 changes: 4 additions & 2 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,17 @@ shibui.pac4j-enabled=false
#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
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=*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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.SpringBootConfiguration
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
Expand Down Expand Up @@ -134,4 +136,9 @@ class TestConfiguration {
return it
}
}

@Bean
public InfoEndpoint getInfoEndpoint() {
return new InfoEndpoint(new ArrayList<InfoContributor>());
}
}
Loading

0 comments on commit a9c8bca

Please sign in to comment.