Skip to content

Commit

Permalink
Merge branch 'master' into for-mary
Browse files Browse the repository at this point in the history
  • Loading branch information
Bill Smith committed Jun 4, 2019
2 parents b14fec1 + 14b2f19 commit 650b0d1
Show file tree
Hide file tree
Showing 86 changed files with 24,136 additions and 5,301 deletions.
3 changes: 3 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ dependencies {
compile 'com.opencsv:opencsv:4.4'

testCompile 'org.skyscreamer:jsonassert:1.5.0'

// Envers for persistent entities versioning
compile 'org.hibernate:hibernate-envers'
}

def generatedSrcDir = new File(buildDir, 'generated/src/main/java')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchService;
import edu.internet2.tier.shibboleth.admin.ui.service.EntityIdsSearchServiceImpl;
import edu.internet2.tier.shibboleth.admin.ui.service.EntityService;
import edu.internet2.tier.shibboleth.admin.ui.service.FileCheckingFileWritingService;
import edu.internet2.tier.shibboleth.admin.ui.service.FileWritingService;
import edu.internet2.tier.shibboleth.admin.ui.service.FilterService;
import edu.internet2.tier.shibboleth.admin.ui.service.FilterTargetService;
import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl;
Expand Down Expand Up @@ -98,13 +100,13 @@ public AttributeUtility attributeUtility() {
@Bean
@ConditionalOnProperty(name = "shibui.metadata-dir")
public EntityDescriptorFilesScheduledTasks entityDescriptorFilesScheduledTasks(EntityDescriptorRepository entityDescriptorRepository, @Value("${shibui.metadata-dir}") final String metadataDir) {
return new EntityDescriptorFilesScheduledTasks(metadataDir, entityDescriptorRepository, openSamlObjects());
return new EntityDescriptorFilesScheduledTasks(metadataDir, entityDescriptorRepository, openSamlObjects(), fileWritingService());
}

@Bean
@ConditionalOnProperty(name = "shibui.metadataProviders.target")
public MetadataProvidersScheduledTasks metadataProvidersScheduledTasks(@Value("${shibui.metadataProviders.target}") final Resource resource, final MetadataResolverService metadataResolverService) {
return new MetadataProvidersScheduledTasks(resource, metadataResolverService);
return new MetadataProvidersScheduledTasks(resource, metadataResolverService, fileWritingService());
}

@Bean
Expand Down Expand Up @@ -202,4 +204,9 @@ public ModelRepresentationConversions modelRepresentationConversions() {
public UserService userService(RoleRepository roleRepository, UserRepository userRepository) {
return new UserService(roleRepository, userRepository);
}

@Bean
public FileWritingService fileWritingService() {
return new FileCheckingFileWritingService();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.envers.Audited;

import javax.persistence.Column;
import javax.persistence.Entity;
Expand All @@ -34,6 +35,7 @@
@JsonSubTypes.Type(value=SignatureValidationFilter.class, name="SignatureValidation"),
@JsonSubTypes.Type(value=RequiredValidUntilFilter.class, name="RequiredValidUntil"),
@JsonSubTypes.Type(value=NameIdFormatFilter.class, name="NameIDFormat")})
@Audited
public class MetadataFilter extends AbstractAuditable {

@JsonProperty("@type")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.envers.Audited;

import javax.persistence.CascadeType;
import javax.persistence.ElementCollection;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package edu.internet2.tier.shibboleth.admin.ui.domain.resolvers;

import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.envers.AuditOverride;
import org.hibernate.envers.Audited;

import javax.persistence.Embedded;
import javax.persistence.Entity;
Expand All @@ -13,6 +16,8 @@
@Getter
@Setter
@ToString
@Audited
@AuditOverride(forClass = AbstractAuditable.class)
public class FileBackedHttpMetadataResolver extends MetadataResolver {
public FileBackedHttpMetadataResolver() {
type = "FileBackedHttpMetadataResolver";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.envers.Audited;

import javax.persistence.CascadeType;
import javax.persistence.Column;
Expand All @@ -37,6 +38,7 @@
@JsonSubTypes.Type(value = DynamicHttpMetadataResolver.class, name = "DynamicHttpMetadataResolver"),
@JsonSubTypes.Type(value = FilesystemMetadataResolver.class, name = "FilesystemMetadataResolver"),
@JsonSubTypes.Type(value = ResourceBackedMetadataResolver.class, name = "ResourceBackedMetadataResolver")})
@Audited
public class MetadataResolver extends AbstractAuditable {

@JsonProperty("@type")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package edu.internet2.tier.shibboleth.admin.ui.envers;

import lombok.Getter;
import lombok.Setter;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;

import javax.persistence.Entity;

/**
* Extension of the default envers revision entity to track authenticated principals
*/
@Entity
@RevisionEntity(PrincipalEnhancingRevisionListener.class)
@Getter
@Setter
public class PrincipalAwareRevisionEntity extends DefaultRevisionEntity {

private String principalUserName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package edu.internet2.tier.shibboleth.admin.ui.envers;

import org.hibernate.envers.RevisionListener;

import static edu.internet2.tier.shibboleth.admin.ui.security.springsecurity.PrincipalAccessor.currentPrincipalIfLoggedIn;

/**
* Implementation of envers revision listener to enhance revision entity with authenticated principal username.
*/
public class PrincipalEnhancingRevisionListener implements RevisionListener {

private static final String ANONYMOUS = "anonymous";

@Override
public void newRevision(Object revisionEntity) {
PrincipalAwareRevisionEntity rev = (PrincipalAwareRevisionEntity) revisionEntity;
String user = currentPrincipalIfLoggedIn().orElse(ANONYMOUS);
rev.setPrincipalUserName(user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor;
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.FileWritingService;
import org.bouncycastle.util.encoders.Hex;
import org.opensaml.core.xml.io.MarshallingException;
import org.slf4j.Logger;
Expand Down Expand Up @@ -34,7 +35,7 @@
* @since 1.0
*/
@Configuration
@ConditionalOnProperty(name = "shibui-metadata-dir")
@ConditionalOnProperty(name = "shibui.metadata-dir")
public class EntityDescriptorFilesScheduledTasks {

private static final Logger LOGGER = LoggerFactory.getLogger(EntityDescriptorFilesScheduledTasks.class);
Expand All @@ -49,12 +50,16 @@ public class EntityDescriptorFilesScheduledTasks {

private static final String TARGET_FILE_TEMPLATE = "%s/%s";

private final FileWritingService fileWritingService;

public EntityDescriptorFilesScheduledTasks(String metadataDirName,
EntityDescriptorRepository entityDescriptorRepository,
OpenSamlObjects openSamlObjects) {
OpenSamlObjects openSamlObjects,
FileWritingService fileWritingService) {
this.metadataDirName = metadataDirName;
this.entityDescriptorRepository = entityDescriptorRepository;
this.openSamlObjects = openSamlObjects;
this.fileWritingService = fileWritingService;
}

@Scheduled(fixedRateString = "${shibui.taskRunRate:30000}")
Expand All @@ -71,7 +76,7 @@ public void generateEntityDescriptorFiles() throws MarshallingException {

try {
String xmlContent = this.openSamlObjects.marshalToXmlString(ed);
Files.write(targetFilePath, xmlContent.getBytes());
fileWritingService.write(targetFilePath, xmlContent);
} catch (MarshallingException | IOException e) {
//TODO: any other better way to handle it?
LOGGER.error("Error marshalling entity descriptor into a file {} - {}", ed.getEntityID(), e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.internet2.tier.shibboleth.admin.ui.scheduled;

import edu.internet2.tier.shibboleth.admin.ui.service.FileWritingService;
import edu.internet2.tier.shibboleth.admin.ui.service.MetadataResolverService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -18,6 +19,7 @@
import javax.xml.transform.stream.StreamResult;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;

@Configuration
@ConditionalOnProperty("shibui.metadataProviders.target")
Expand All @@ -26,22 +28,25 @@ public class MetadataProvidersScheduledTasks {

private final Resource target;
private final MetadataResolverService metadataResolverService;
private final FileWritingService fileWritingService;

public MetadataProvidersScheduledTasks(Resource target, MetadataResolverService metadataResolverService) {
public MetadataProvidersScheduledTasks(Resource target, MetadataResolverService metadataResolverService, FileWritingService fileWritingService) {
this.target = target;
this.metadataResolverService = metadataResolverService;
this.fileWritingService = fileWritingService;
}

@Scheduled(fixedRateString = "${shibui.metadataProviders.taskRunRate:30000}")
@Transactional(readOnly = true)
public void generateMetadataProvidersFile() {
try (OutputStream os = ((WritableResource)target).getOutputStream()) {
try (StringWriter os = new StringWriter()) {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");


transformer.transform(new DOMSource(metadataResolverService.generateConfiguration()), new StreamResult(os));
this.fileWritingService.write((WritableResource)this.target, os.toString());
} catch (IOException | TransformerException e) {
logger.error(e.getLocalizedMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package edu.internet2.tier.shibboleth.admin.ui.security.springsecurity;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.Optional;

public final class PrincipalAccessor {

//Non-instantiable utility class
private PrincipalAccessor() {
}

public static Optional<String> currentPrincipalIfLoggedIn() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return Optional.empty();
}
return Optional.of(authentication.getName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package edu.internet2.tier.shibboleth.admin.ui.service;

import org.springframework.core.io.WritableResource;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

public class FileCheckingFileWritingService implements FileWritingService {
private static final String DEFAULT_ALGORITHM = "MD5";
private final String algorithm;

public FileCheckingFileWritingService() {
this(DEFAULT_ALGORITHM);
}

public FileCheckingFileWritingService(String algorithm) {
this.algorithm = algorithm;
}

@Override
public void write(Path path, String content) throws IOException {
if (Files.exists(path)) {
try (InputStream is = Files.newInputStream(path)) {
if (checkContentMatches(is, content)) {
return;
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
writeContent(path, content);
}

@Override
public void write(WritableResource resource, String content) throws IOException {
if (resource.exists()) {
try (InputStream is = resource.getInputStream()) {
if (checkContentMatches(is, content)) {
return;
}
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
writeContent(resource, content);
}

private boolean checkContentMatches(InputStream inputStream, String content) throws NoSuchAlgorithmException, IOException {
MessageDigest md = MessageDigest.getInstance(this.algorithm);
try (DigestInputStream dis = new DigestInputStream(inputStream, md)) {
byte[] buf = new byte[4096];
while (dis.read(buf) > -1) {}
}
byte[] fileDigest = md.digest();
byte[] contentDigest = md.digest(content.getBytes());
return Arrays.equals(fileDigest, contentDigest);
}

void writeContent(Path path, String content) throws IOException {
Files.write(path, content.getBytes());
}

void writeContent(WritableResource resource, String content) throws IOException {
try (OutputStream os = resource.getOutputStream()) {
os.write(content.getBytes());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package edu.internet2.tier.shibboleth.admin.ui.service;

import org.springframework.core.io.WritableResource;

import java.io.IOException;
import java.nio.file.Path;

/**
* Service interface for writing files. Implementations may perform various tasks
* before or after writing the file.
*/
public interface FileWritingService {
/**
* Write content to a file
*
* @param path target file Path
* @param content content to write
* @throws IOException
*/
void write(Path path, String content) throws IOException;

/**
* Write content to a writeable resource
*
* @param resource
* @param content
* @throws IOException
*/
void write(WritableResource resource, String content) throws IOException;
}
3 changes: 3 additions & 0 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ spring.jpa.properties.hibernate.format_sql=false

spring.jpa.hibernate.use-new-id-generator-mappings=true

#Envers versioning
spring.jpa.properties.org.hibernate.envers.store_data_at_delete=true

# Set the following property to periodically write out the generated metadata files. There is no default value; the following is just an example
# shibui.metadata-dir=/opt/shibboleth-idp/metadata/generated
shibui.logout-url=/dashboard
Expand Down
12 changes: 12 additions & 0 deletions backend/src/main/resources/i18n/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,18 @@ label.email=Email
label.role=Role
label.delete=Delete?

label.metadata-resolver-history=Metadata resolver history
label.metadata-version-history=Metadata Version History
label.select-version=Select Version
label.version=Version
label.save-date=Save Date
label.changed-by=Changed By
label.actions=Actions
label.check-to-select=Check to select
label.current=Current
label.restore=Restore
label.compare-selected=Compare Selected

message.delete-user-title=Delete User?
message.delete-user-body=You are requesting to delete a user. If you complete this process the user will be removed. This cannot be undone. Do you wish to continue?

Expand Down
Loading

0 comments on commit 650b0d1

Please sign in to comment.