Skip to content

Commit

Permalink
Merged in feature/shibui-2568 (pull request #657)
Browse files Browse the repository at this point in the history
Feature/shibui 2568

Approved-by: Chad Redman
  • Loading branch information
chasegawa committed Jun 1, 2023
2 parents ef6bd77 + 4f19807 commit b2de084
Show file tree
Hide file tree
Showing 51 changed files with 677 additions and 396 deletions.
3 changes: 0 additions & 3 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
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
@@ -1,8 +1,11 @@
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.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;
Expand All @@ -19,6 +22,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 +33,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 @@ -41,20 +46,28 @@
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.health.HealthEndpoint;
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;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.io.Resource;
import org.springframework.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;
Expand All @@ -64,6 +77,12 @@

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
@Import(SearchConfiguration.class)
Expand Down Expand Up @@ -254,4 +273,49 @@ 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,
EntityDescriptorRepository entityDescriptorRepository, MetadataResolverRepository metadataResolverRepository, FilterRepository filterRepository,
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;
}

@Bean
public List<URL> beaconEndpointUrl(@Value("${shibui.beacon.url}") String urls) throws MalformedURLException {
List<URL> result = new ArrayList<>();
for (String url : Arrays.asList(urls.split(","))) {
result.add(new URL(url));
}
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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,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;
Expand Down Expand Up @@ -80,6 +84,7 @@ public AuditorAware<String> defaultAuditorAware() {
@Bean
@Profile("!no-auth")
public WebSecurityConfigurerAdapter defaultAuth() {
BeaconConfiguration.setAuthMechanisms("default");
return new WebSecurityConfigurerAdapter() {

@Override
Expand All @@ -88,7 +93,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"))
Expand Down
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
@@ -0,0 +1,34 @@
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;

@Profile({"dev", "very-dangerous"})
@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");
}
}
Original file line number Diff line number Diff line change
@@ -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;
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,35 @@
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
public class BeaconConfiguration {
@Id
private int id = 1;

private String installationId;

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
*/
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
Expand Up @@ -60,4 +60,6 @@ public interface EntityDescriptorRepository extends JpaRepository<EntityDescript
" and e.approved = false")
List<EntityDescriptorProjection> getEntityDescriptorsNeedingApproval(@Param("groupIds") List<String> groupIds);

@Query("SELECT COUNT(ed) FROM EntityDescriptor ed WHERE ed.serviceEnabled = true")
int getActiveEntityCount();
}
Original file line number Diff line number Diff line change
@@ -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, Long> {
MetadataFilter findByResourceId(String resourceId);
}

@Query("SELECT COUNT(f) FROM MetadataFilter f WHERE f.filterEnabled = true")
int getActiveFilterCount();
}
Loading

0 comments on commit b2de084

Please sign in to comment.