Skip to content

Commit

Permalink
Merge branch 'master' of bitbucket.org:unicon/shib-idp-ui into react-…
Browse files Browse the repository at this point in the history
…support
  • Loading branch information
rmathis committed May 21, 2021
2 parents e374407 + 8fda70e commit e846dec
Show file tree
Hide file tree
Showing 13 changed files with 598 additions and 40 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ rdurable
build-no-tests

beacon/spring/out
/.classpath

# Eclipse junk
*.classpath
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package edu.internet2.tier.shibboleth.admin.ui.configuration;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;

@Configuration
public class ETagsConfiguration {
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns("/api/entities/*", "/entities/*");
filterRegistrationBean.setName("etagFilter");
return filterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ public class WebSecurityConfig {
@Autowired
private RoleRepository roleRepository;

@Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
private HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true);
firewall.setAllowUrlEncodedDoubleSlash(true);
return firewall;
}

@Bean
public HttpFirewall defaultFirewall() {
private HttpFirewall defaultFirewall() {
return new DefaultHttpFirewall();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package edu.internet2.tier.shibboleth.admin.ui.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import edu.internet2.tier.shibboleth.admin.ui.domain.CustomAttributeDefinition;
import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor;
import edu.internet2.tier.shibboleth.admin.ui.service.CustomAttributesService;

@Controller
@RequestMapping(value = "/api/custom")
public class CustomAttributesController {
@Autowired
private CustomAttributesService caService;

@PostMapping("/attribute")
@Transactional
public ResponseEntity<?> create(@RequestBody CustomAttributeDefinition definition) {
// If already defined, we can't create a new one, nor will this call update the definition
CustomAttributeDefinition cad = caService.find(definition.getName());

if (cad != null) {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/attribute").build().toUri());

return ResponseEntity.status(HttpStatus.CONFLICT).headers(headers)
.body(new ErrorResponse(String.valueOf(HttpStatus.CONFLICT.value()),
String.format("The custom attribute definition with name: [%s] already exists.", definition.getName())));
}

CustomAttributeDefinition result = caService.createOrUpdateDefinition(definition);
return ResponseEntity.status(HttpStatus.CREATED).body(result);
}

@PutMapping("/attribute")
@Transactional
public ResponseEntity<?> update(@RequestBody CustomAttributeDefinition definition) {
CustomAttributeDefinition cad = caService.find(definition.getName());
if (cad == null) {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/attribute").build().toUri());

return ResponseEntity.status(HttpStatus.NOT_FOUND).headers(headers)
.body(new ErrorResponse(String.valueOf(HttpStatus.NOT_FOUND.value()),
String.format("The custom attribute definition with name: [%s] does not already exist.", definition.getName())));
}

CustomAttributeDefinition result = caService.createOrUpdateDefinition(definition);
return ResponseEntity.ok(result);
}

@GetMapping("/attributes")
@Transactional(readOnly = true)
public ResponseEntity<?> getAll() {
return ResponseEntity.ok(caService.getAllDefinitions());
}

@GetMapping("/attribute/{name}")
@Transactional(readOnly = true)
public ResponseEntity<?> getOne(@PathVariable String name) {
CustomAttributeDefinition cad = caService.find(name);
if (cad == null) {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(
ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/attribute/" + name).build().toUri());

return ResponseEntity.status(HttpStatus.NOT_FOUND).headers(headers)
.body(new ErrorResponse(String.valueOf(HttpStatus.NOT_FOUND.value()),
String.format("The custom attribute definition with name: [%s] does not already exist.", name)));
}
return ResponseEntity.ok(cad);
}

@DeleteMapping("/attribute/{name}")
@Transactional
public ResponseEntity<?> delete(@PathVariable String name) {
CustomAttributeDefinition cad = caService.find(name);
if (cad == null) {
HttpHeaders headers = new HttpHeaders();
headers.setLocation(
ServletUriComponentsBuilder.fromCurrentServletMapping().path("/api/custom/attribute/" + name).build().toUri());

return ResponseEntity.status(HttpStatus.NOT_FOUND).headers(headers)
.body(new ErrorResponse(String.valueOf(HttpStatus.NOT_FOUND.value()),
String.format("The custom attribute definition with name: [%s] does not already exist.", name)));
}
caService.deleteDefinition(cad);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -1,64 +1,90 @@
package edu.internet2.tier.shibboleth.admin.ui.controller;

import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation;
import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects;
import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
import net.shibboleth.utilities.java.support.resolver.ResolverException;
import org.opensaml.core.criterion.EntityIdCriterion;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.apache.http.client.utils.DateUtils;
import org.opensaml.core.xml.io.MarshallingException;
import org.opensaml.saml.metadata.resolver.MetadataResolver;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation;
import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects;
import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository;
import edu.internet2.tier.shibboleth.admin.ui.service.EntityDescriptorService;
import lombok.extern.slf4j.Slf4j;
import net.shibboleth.utilities.java.support.resolver.ResolverException;

@Controller
@RequestMapping(value = "/api/entities", method = RequestMethod.GET)
@RequestMapping(value = { "/entities", // per protocol - https://spaces.at.internet2.edu/display/MDQ/Metadata+Query+Protocol
"/api/entities" }, // existing - included to break no existing code
method = RequestMethod.GET)
@Slf4j
/**
* EntitiesController is here to meet the requirements for this project being an MDQ. Despite similar logic to the
* EntitiesDescriptorController, the required endpoints that make this project an MDQ server are served by this controller.
*/
public class EntitiesController {
private static final Logger logger = LoggerFactory.getLogger(EntitiesController.class);

@Autowired
private MetadataResolver metadataResolver;

@Autowired
private EntityDescriptorService entityDescriptorService;

@Autowired
private OpenSamlObjects openSamlObjects;

@Autowired
private EntityDescriptorRepository entityDescriptorRepository;

@RequestMapping(value = "{entityId:.*}")
@RequestMapping(value = "/{entityId:.*}")
@Transactional(readOnly = true)
public ResponseEntity<?> getOne(final @PathVariable String entityId, HttpServletRequest request) throws UnsupportedEncodingException, ResolverException {
EntityDescriptor entityDescriptor = this.getEntityDescriptor(entityId);
if (entityDescriptor == null) {
return ResponseEntity.notFound().build();
}
EntityDescriptorRepresentation entityDescriptorRepresentation = entityDescriptorService.createRepresentationFromDescriptor(entityDescriptor);
return ResponseEntity.ok(entityDescriptorRepresentation);
HttpHeaders headers = new HttpHeaders();
headers.set("Last-Modified", formatModifiedDate(entityDescriptorRepresentation));
return new ResponseEntity<>(entityDescriptorRepresentation, headers, HttpStatus.OK);
}

@RequestMapping(value = "{entityId:.*}", produces = "application/xml")
private String formatModifiedDate(EntityDescriptorRepresentation entityDescriptorRepresentation) {
Instant instant = entityDescriptorRepresentation.getModifiedDateAsDate().toInstant(ZoneOffset.UTC);
Date date = Date.from(instant);
return DateUtils.formatDate(date, DateUtils.PATTERN_RFC1123);
}

@RequestMapping(value = "/{entityId:.*}", produces = "application/xml")
@Transactional(readOnly = true)
public ResponseEntity<?> getOneXml(final @PathVariable String entityId) throws MarshallingException, ResolverException, UnsupportedEncodingException {
EntityDescriptor entityDescriptor = this.getEntityDescriptor(entityId);
if (entityDescriptor == null) {
return ResponseEntity.notFound().build();
}
final String xml = this.openSamlObjects.marshalToXmlString(entityDescriptor);
return ResponseEntity.ok(xml);
EntityDescriptorRepresentation entityDescriptorRepresentation = entityDescriptorService.createRepresentationFromDescriptor(entityDescriptor);
HttpHeaders headers = new HttpHeaders();
headers.set("Last-Modified", formatModifiedDate(entityDescriptorRepresentation));
return new ResponseEntity<>(xml, headers, HttpStatus.OK);
}

private EntityDescriptor getEntityDescriptor(final String entityId) throws ResolverException, UnsupportedEncodingException {
String decodedEntityId = URLDecoder.decode(entityId, "UTF-8");
EntityDescriptor entityDescriptor = this.metadataResolver.resolveSingle(new CriteriaSet(new EntityIdCriterion(decodedEntityId)));
EntityDescriptor entityDescriptor = entityDescriptorRepository.findByEntityID(decodedEntityId);
// TODO: we need to clean this up sometime
if (entityDescriptor instanceof edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor) {
((edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor) entityDescriptor).setResourceId(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package edu.internet2.tier.shibboleth.admin.ui.domain;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;

import org.hibernate.envers.Audited;

import lombok.Data;

@Entity(name = "custom_attribute_definition")
@Audited
@Data
public class CustomAttributeDefinition {
@Id
@Column(nullable = false)
String name;

@Column(name = "help_text", nullable = true)
String helpText;

@Column(name = "attribute_type", nullable = false)
CustomAttributeType attributeType;

@Column(name = "default_value", nullable = true)
String defaultValue;

@ElementCollection
@CollectionTable(name = "custom_attr_list_defs", joinColumns = @JoinColumn(name = "name"))
@Column(name = "value", nullable = false)
Set<String> customAttrListDefinitions = new HashSet<>();

// @TODO: logic to ensure defaultValue matches an item from the list of values when SELECTION_LIST is the type ??
}

enum CustomAttributeType {
BOOLEAN, INTEGER, LONG, DOUBLE, DURATION, SELECTION_LIST, SPRING_BEAN_ID
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.internet2.tier.shibboleth.admin.ui.domain.frontend;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

Expand Down Expand Up @@ -188,6 +189,12 @@ public void setCreatedDate(LocalDateTime createdDate) {
public String getModifiedDate() {
return modifiedDate != null ? modifiedDate.toString() : null;
}

@JsonIgnore
public LocalDateTime getModifiedDateAsDate() {
// we shouldn't have an ED without either modified or created date, so this is mostly for testing where data can be odd
return modifiedDate != null ? modifiedDate : createdDate != null ? createdDate : LocalDateTime.now();
}

public void setModifiedDate(LocalDateTime modifiedDate) {
this.modifiedDate = modifiedDate;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package edu.internet2.tier.shibboleth.admin.ui.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import edu.internet2.tier.shibboleth.admin.ui.domain.CustomAttributeDefinition;

/**
* Repository to manage {@link CustomAttributeDefinition} instances.
*/
public interface CustomAttributeRepository extends JpaRepository<CustomAttributeDefinition, String> {

List<CustomAttributeDefinition> findAll();

CustomAttributeDefinition findByName(String name);

@SuppressWarnings("unchecked")
CustomAttributeDefinition save(CustomAttributeDefinition attribute);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package edu.internet2.tier.shibboleth.admin.ui.service;

import java.util.List;

import edu.internet2.tier.shibboleth.admin.ui.domain.CustomAttributeDefinition;

public interface CustomAttributesService {

CustomAttributeDefinition createOrUpdateDefinition(CustomAttributeDefinition definition);

void deleteDefinition(CustomAttributeDefinition definition);

CustomAttributeDefinition find(String name);

List<CustomAttributeDefinition> getAllDefinitions();

}
Loading

0 comments on commit e846dec

Please sign in to comment.