Skip to content

Commit

Permalink
Merged in feature/SHIBUI-1253 (pull request #323)
Browse files Browse the repository at this point in the history
[SHIBUI-1253]

Approved-by: Dmitriy Kopylenko <dkopylenko@unicon.net>
  • Loading branch information
Jonathan Johnson committed May 30, 2019
2 parents 2b64441 + 714a7da commit 7af5147
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 8 deletions.
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 @@ -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,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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorReposit
import edu.internet2.tier.shibboleth.admin.ui.security.repository.RoleRepository
import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository
import edu.internet2.tier.shibboleth.admin.ui.security.service.UserService
import edu.internet2.tier.shibboleth.admin.ui.service.FileCheckingFileWritingService
import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityDescriptorServiceImpl
import edu.internet2.tier.shibboleth.admin.ui.service.JPAEntityServiceImpl
import edu.internet2.tier.shibboleth.admin.ui.util.RandomGenerator
Expand Down Expand Up @@ -57,7 +58,7 @@ class EntityDescriptorFilesScheduledTasksTests extends Specification {
randomGenerator = new RandomGenerator()
tempPath = tempPath + randomGenerator.randomRangeInt(10000, 20000)
service = new JPAEntityDescriptorServiceImpl(openSamlObjects, new JPAEntityServiceImpl(openSamlObjects), new UserService(roleRepository, userRepository))
entityDescriptorFilesScheduledTasks = new EntityDescriptorFilesScheduledTasks(tempPath, entityDescriptorRepository, openSamlObjects)
entityDescriptorFilesScheduledTasks = new EntityDescriptorFilesScheduledTasks(tempPath, entityDescriptorRepository, openSamlObjects, new FileCheckingFileWritingService())
directory = new File(tempPath)
directory.mkdir()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package edu.internet2.tier.shibboleth.admin.ui.service

import org.springframework.core.io.PathResource
import org.springframework.core.io.WritableResource
import spock.lang.Specification

import java.nio.file.Files
import java.nio.file.Path
import java.security.NoSuchAlgorithmException

class FileCheckingFileWritingServiceTests extends Specification {
def writer = Spy(FileCheckingFileWritingService)

Path file

WritableResource resource

def setup() {
file = Files.createTempFile('test1', '.txt')
resource = new PathResource(file)
}

def 'test bad algorithm'() {
setup:
def badWriter = new FileCheckingFileWritingService('badAlGoreRhythm')

when:
badWriter.write(Files.createTempFile('testbadalgorithm', '.txt'), 'bad')

then:
RuntimeException ex = thrown()
assert ex.cause instanceof NoSuchAlgorithmException
}

def 'test a single write to a Path'() {
when:
writer.write(file, 'testme')

then:
1 * writer.writeContent(file, 'testme')
assert file.text == 'testme'
}

def 'test writes with changed content to a Path'() {
when:
writer.write(file, 'testme')
writer.write(file, 'anothertest')

then:
1 * writer.writeContent(file, 'testme')
1 * writer.writeContent(file, 'anothertest')
assert file.text == 'anothertest'
}

def 'test writes with unchanged content, should only write once to a Path'() {
when:
(1..5).each {
writer.write(file, 'testme2')
}

then:
1 * writer.writeContent(file, 'testme2')
assert file.text == 'testme2'
}

def 'test a single write to a WriteableResource'() {
when:
writer.write(resource, 'testme')

then:
1 * writer.writeContent(resource, 'testme')
assert resource.getFile().text == 'testme'
}

def 'test write with changed content to a WritableResource'() {
when:
writer.write(resource, 'testme')
writer.write(resource, 'anothertest')

then:
1 * writer.writeContent(resource, 'testme')
1 * writer.writeContent(resource, 'anothertest')
assert resource.getFile().text == 'anothertest'
}

def 'test writes with unchanged content, should only write once to a WriteableResource'() {
when:
(1..5).each {
writer.write(resource, 'testme2')
}

then:
1 * writer.writeContent(resource, 'testme2')
assert resource.getFile().text == 'testme2'
}
}

0 comments on commit 7af5147

Please sign in to comment.