Skip to content

Commit

Permalink
Merge branch 'master' of bitbucket.org:unicon/shib-idp-ui into featur…
Browse files Browse the repository at this point in the history
…e/SHIBUI-1267
  • Loading branch information
rmathis committed Jun 10, 2019
2 parents 2c27f1c + c5b4386 commit 63f9277
Show file tree
Hide file tree
Showing 15 changed files with 243 additions and 45 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'
}
}
2 changes: 2 additions & 0 deletions ui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ node {
download = true
}

npmInstall.setNpmCommand('ci')

npm_run_build {
inputs.dir 'src'
outputs.dir 'dist'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,6 @@ describe('Provider Edit Component', () => {
expect(app).toBeTruthy();
}));

describe('go method', () => {
it('should route to the given child form', () => {
spyOn(router, 'navigate');
app.go('common');
expect(router.navigate).toHaveBeenCalled();
});
});

describe('save method', () => {
it('should route to the given child form', () => {
app.save();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ export class ProviderEditComponent implements OnDestroy, CanComponentDeactivate
let startIndex$ = this.route.firstChild.params.pipe(map(p => p.form || 'filters'));
startIndex$.subscribe(index => this.store.dispatch(new SetIndex(index)));

this.index$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(id => id && this.go(id));

this.store
.select(fromWizard.getCurrentWizardSchema)
.pipe(filter(s => !!s))
Expand All @@ -77,10 +75,6 @@ export class ProviderEditComponent implements OnDestroy, CanComponentDeactivate
this.canFilter$ = this.definition$.pipe(map(def => FilterableProviders.indexOf(def.type) > -1));
}

go(id: string): void {
this.router.navigate(['./', id], { relativeTo: this.route });
}

ngOnDestroy() {
this.clear();
this.ngUnsubscribe.next();
Expand Down
Loading

0 comments on commit 63f9277

Please sign in to comment.