Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jj! committed Apr 30, 2018
2 parents 4949c3f + 85e508b commit 061dada
Show file tree
Hide file tree
Showing 15 changed files with 249 additions and 46 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ allowed with:
-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
```

In Apache HTTPD, you'll need something like:

```
<VirtualHost *:80>
AllowEncodedSlashes NoDecode
ServerName shibui.unicon.net
ProxyPass / http://localhost:8080/ nocanon
ProxyPassReverse / http://localhost:8080/
</VirtualHost>
```

### Running as an executable

`java -jar shibui.war`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package edu.internet2.tier.shibboleth.admin.ui.configuration;

import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;

@Component
public class TomcatConfiguration implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
package edu.internet2.tier.shibboleth.admin.ui.controller

import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects
import net.shibboleth.ext.spring.resource.ResourceHelper
import org.joda.time.DateTime
import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver
import org.opensaml.saml.metadata.resolver.MetadataResolver
import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain
import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.core.io.ClassPathResource
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.web.util.DefaultUriBuilderFactory
import org.xmlunit.builder.DiffBuilder
import org.xmlunit.builder.Input
import org.xmlunit.diff.DefaultNodeMatcher
import org.xmlunit.diff.ElementSelectors
import spock.lang.Specification

/**
* @author Bill Smith (wsmith@unicon.net)
*/
// TODO: implement
// @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
// @ActiveProfiles("no-auth")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("no-auth")
class EntitiesControllerIntegrationTests extends Specification {

@Autowired
private WebTestClient webClient

def setup() {
// yeah, don't ask... this is just shenanigans
this.webClient.webClient.uriBuilderFactory.encodingMode = DefaultUriBuilderFactory.EncodingMode.NONE
}

def "GET /api/entities returns the proper json"() {
given:
def expectedBody = '''
Expand Down Expand Up @@ -55,24 +74,108 @@ class EntitiesControllerIntegrationTests extends Specification {
'''

when:
def x = 1
// TODO: implement
/*
def result = this.webClient
.get()
.uri("/api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1")
.exchange()
*/
.exchange() // someday, I'd like to know why IntelliJ "cannot resolve symbol 'exchange'"

then:
assert 1
// TODO: implement
/*
result.expectStatus().isOk()
.expectBody().consumeWith(
{ response -> new String(response.getResponseBody()) == expectedBody }
)
//.expectedBody() // some other json comparison
*/
.expectBody()
.json(expectedBody)
}

def "GET /api/entities/test is not found"() {
when:
def result = this.webClient
.get()
.uri("/api/entities/test")
.exchange()

then:
result.expectStatus().isNotFound()
}

def "GET /api/entities/test XML is not found"() {
when:
def result = this.webClient
.get()
.uri("/api/entities/test")
.header('Accept', 'application/xml')
.exchange()

then:
result.expectStatus().isNotFound()
}

def "GET /api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1 XML returns proper XML"() {
given:
def expectedBody = '''<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://test.scaldingspoon.org/test1">
<md:Extensions>
<mdattr:EntityAttributes xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute">
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="http://scaldingspoon.org/realm" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue>internal</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="http://shibboleth.net/ns/attributes/releaseAllValues" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue>givenName</saml:AttributeValue>
<saml:AttributeValue>employeeNumber</saml:AttributeValue>
</saml:Attribute>
</mdattr:EntityAttributes>
</md:Extensions>
<md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://test.scaldingspoon.org/test1/acs" index="1"/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
'''
when:
def result = this.webClient
.get()
.uri("/api/entities/http%3A%2F%2Ftest.scaldingspoon.org%2Ftest1")
.header('Accept', 'application/xml')
.exchange()

then:
def resultBody = result.expectStatus().isOk()
//.expectHeader().contentType("application/xml;charset=ISO-8859-1") // should this really be ISO-8859-1?
// expectedBody encoding is UTF-8...
.expectHeader().contentType("application/xml;charset=UTF-8")
.expectBody(String.class)
.returnResult()
def diff = DiffBuilder.compare(Input.fromString(expectedBody)).withTest(Input.fromString(resultBody.getResponseBody())).ignoreComments().checkForSimilar().ignoreWhitespace().withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)).build()
!diff.hasDifferences()
}

@TestConfiguration
static class Config {
@Autowired
OpenSamlObjects openSamlObjects

@Bean
MetadataResolver metadataResolver() {
def resource = ResourceHelper.of(new ClassPathResource("/metadata/aggregate.xml"))
def aggregate = new ResourceBackedMetadataResolver(resource){
@Override
DateTime getLastRefresh() {
return null
}
}

aggregate.with {
it.metadataFilter = new MetadataFilterChain()
it.id = 'testme'
it.parserPool = openSamlObjects.parserPool
it.initialize()
it
}

return new ChainingMetadataResolver().with {
it.id = 'chain'
it.resolvers = [aggregate]
it.initialize()
it
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ <h4 class="modal-title">
class="form-control"
aria-label="Entity ID search text box"
/>
<small class="form-text text-muted" i18n="@@label--min-4-chars">
Minimum 4 characters.
</small>
</div>
<div class="form-group col-3">
<button class="btn btn-primary btn-block"
(click)="search(form.get('search').value)"
aria-label="Search Entity ID"
[ngSwitch]="loading$ | async">
[ngSwitch]="loading$ | async"
[disabled]="form.get('search').invalid">
<i class="fa fa-search" *ngSwitchDefault></i>
<i class="fa fa-spinner fa-pulse fa-fw" *ngSwitchCase="true"></i>
<ng-container i18n="@@action--search">Search</ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Store } from '@ngrx/store';

import * as fromFilter from '../reducer';
import { QueryEntityIds } from '../action/search.action';
import { FormBuilder, FormGroup } from '@angular/forms';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { map } from 'rxjs/operators';

@Component({
selector: 'search-dialog',
Expand All @@ -26,7 +27,7 @@ export class SearchDialogComponent implements OnInit, AfterViewInit {
dbounce = 500;

form: FormGroup = this.fb.group({
search: [''],
search: ['', [Validators.minLength(4)]],
entityId: ['']
});

Expand Down
58 changes: 38 additions & 20 deletions ui/src/app/metadata-filter/container/edit-filter.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,28 @@
</div>
<label for="entityId" class="sr-only">Search Entity ID</label>
<div class="row">
<auto-complete id="entityId"
#entityInput
class="component-control col-10"
formControlName="entityId"
limit="10"
[matches]="entityIds$ | async"
[required]="true"
[processing]="(processing$ | async)"
(more)="onViewMore($event)">
</auto-complete>
<div class="col-2">
<div class="col-9">
<auto-complete id="entityId"
#entityInput
class="component-control"
formControlName="entityId"
limit="10"
[matches]="entityIds$ | async"
[required]="true"
[processing]="(processing$ | async)"
(more)="onViewMore($event)">
</auto-complete>
<small class="form-text text-muted" i18n="@@label--min-4-chars">
Minimum 4 characters.
</small>
<ng-container *ngIf="form.get('entityId').touched && form.get('entityId').invalid">
<small class="form-text text-danger">
<ng-container *ngIf="form.get('entityId').hasError('required')" i18n="@@message--entity-id-required">Entity ID is required</ng-container>
<ng-container *ngIf="form.get('entityId').hasError('exists')" i18n="@@message--entity-id-not-found">Entity ID not found</ng-container>
</small>
</ng-container>
</div>
<div class="col-3">
<button
*ngIf="form.get('entityId').disabled"
class="btn btn-block btn-secondary"
Expand All @@ -85,12 +96,6 @@
</button>
</div>
</div>
<ng-container *ngIf="form.get('entityId').touched && form.get('entityId').invalid">
<small class="form-text text-danger">
<ng-container *ngIf="form.get('entityId').hasError('required')" i18n="@@message--entity-id-required">Entity ID is required</ng-container>
<ng-container *ngIf="form.get('entityId').hasError('exists')" i18n="@@message--entity-id-not-found">Entity ID not found</ng-container>
</small>
</ng-container>
</div>
</fieldset>
</div>
Expand All @@ -99,11 +104,24 @@
<i class="fa fa-eye fa-lg"></i>
<ng-container i18n="@@action--save-changes">Preview XML</ng-container>
</button>
<button (click)="this.save()" type="submit" class="btn btn-primary" [disabled]="form.invalid">
<i class="fa fa-save fa-lg"></i>
<button
(click)="this.save()"
type="submit"
class="btn btn-primary"
[disabled]="form.invalid || (isSaving$ | async)">
<i class="fa fa-lg"
[ngClass]="{
'fa-save': !(isSaving$ | async),
'fa-spinner': (isSaving$ | async),
'fa-pulse': (isSaving$ | async)
}"></i>
<ng-container i18n="@@action--save-changes">Save Changes</ng-container>
</button>
<button (click)="this.cancel()" type="reset" class="btn btn-secondary">
<button
(click)="this.cancel()"
type="reset"
class="btn btn-secondary"
[disabled]="isSaving$ | async ">
<ng-container i18n="@@action--cancel">Cancel</ng-container>
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class EditFilterComponent implements OnInit, OnDestroy {
loading$: Observable<boolean>;
processing$: Observable<boolean>;
preview$: Observable<MDUI>;
isSaving$: Observable<boolean>;

form: FormGroup = this.fb.group({
entityId: ['', [Validators.required]],
Expand All @@ -55,6 +56,7 @@ export class EditFilterComponent implements OnInit, OnDestroy {
});

filter: MetadataFilter;
filterEntity: Filter;

isValid = false;

Expand All @@ -75,6 +77,7 @@ export class EditFilterComponent implements OnInit, OnDestroy {
this.loading$ = this.store.select(fromFilter.getIsLoading);
this.processing$ = this.loading$.withLatestFrom(this.showMore$, (l, s) => !s && l);
this.preview$ = this.store.select(fromFilter.getPreview);
this.isSaving$ = this.store.select(fromFilter.getSaving);

this.entityIds$.subscribe(ids => this.ids = ids);

Expand All @@ -86,6 +89,7 @@ export class EditFilterComponent implements OnInit, OnDestroy {
filterEnabled
});
this.filter = filter;
this.filterEntity = new Filter(filter);

this.store.dispatch(new SelectId(entityId));
});
Expand Down Expand Up @@ -139,7 +143,7 @@ export class EditFilterComponent implements OnInit, OnDestroy {
const input = this.form.get('entityId');
input.disable();
input.clearAsyncValidators();
input.reset(this.filter.entityId);
input.setValue(this.filterEntity.entityId);
}

searchEntityIds(term: string): void {
Expand Down
20 changes: 17 additions & 3 deletions ui/src/app/metadata-filter/container/new-filter.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
<auto-complete id="entityId" class="component-control" formControlName="entityId" limit="10" [matches]="entityIds$ | async"
[required]="true" [processing]="(processing$ | async)" (more)="onViewMore($event)">
</auto-complete>
<small class="form-text text-muted" i18n="@@label--min-4-chars">
Minimum 4 characters.
</small>
<ng-container *ngIf="form.get('entityId').touched && form.get('entityId').invalid">
<small class="form-text text-danger">
<ng-container *ngIf="form.get('entityId').hasError('required')" i18n="@@message--entity-id-required">Entity ID is required</ng-container>
Expand All @@ -70,11 +73,22 @@
</fieldset>
</div>
<div class="col-lg-6 col-xs-3 text-right">
<button (click)="this.save()" type="submit" class="btn btn-primary" [disabled]="form.invalid">
<i class="fa fa-save fa-lg"></i>
<button (click)="this.save()"
type="submit"
class="btn btn-primary"
[disabled]="form.invalid || (isSaving$ | async)">
<i class="fa fa-lg"
[ngClass]="{
'fa-save': !(isSaving$ | async),
'fa-spinner': (isSaving$ | async),
'fa-pulse': (isSaving$ | async)
}"></i>
<ng-container i18n="@@action--save-changes">Save Changes</ng-container>
</button>
<button (click)="this.cancel()" type="reset" class="btn btn-secondary">
<button (click)="this.cancel()"
type="reset"
class="btn btn-secondary"
[disabled]="isSaving$ | async">
<ng-container i18n="@@action--cancel">Cancel</ng-container>
</button>
</div>
Expand Down
Loading

0 comments on commit 061dada

Please sign in to comment.