Skip to content

Commit

Permalink
Merged in bugfix/shibui-2364 (pull request #615)
Browse files Browse the repository at this point in the history
SHIBUI-2364

Approved-by: Doug Sonaty
  • Loading branch information
chasegawa committed Sep 12, 2022
2 parents 64e4fdd + 999ba4f commit 4ff0410
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public ResponseEntity<?> deleteOne(@PathVariable String resourceId) throws Forbi
@GetMapping("/EntityDescriptors")
@Transactional
public ResponseEntity<?> getAll() throws ForbiddenException {
return ResponseEntity.ok(entityDescriptorService.getAllRepresentationsBasedOnUserAccess());
return ResponseEntity.ok(entityDescriptorService.getAllEntityDescriptorProjectionsBasedOnUserAccess());
}

@GetMapping("/EntityDescriptor/{resourceId}/Versions")
Expand Down Expand Up @@ -145,13 +145,20 @@ public void initRestTemplate() {
@PutMapping("/EntityDescriptor/{resourceId}")
@Transactional
public ResponseEntity<?> update(@RequestBody EntityDescriptorRepresentation edRepresentation, @PathVariable String resourceId)
throws ForbiddenException, ConcurrentModificationException, PersistentEntityNotFound,
InvalidPatternMatchException {
throws ForbiddenException, ConcurrentModificationException, PersistentEntityNotFound, InvalidPatternMatchException {
edRepresentation.setId(resourceId); // This should be the same already, but just to be safe...
EntityDescriptorRepresentation result = entityDescriptorService.update(edRepresentation);
return ResponseEntity.ok().body(result);
}

@PutMapping("/EntityDescriptor/{resourceId}/changeGroup/{groupId}")
@Transactional
public ResponseEntity<?> updateGroupForEntityDescriptor(@PathVariable String resourceId, @PathVariable String groupId)
throws ForbiddenException, ConcurrentModificationException, PersistentEntityNotFound, InvalidPatternMatchException {
EntityDescriptorRepresentation result = entityDescriptorService.updateGroupForEntityDescriptor(resourceId, groupId);
return ResponseEntity.ok().body(result);
}

@PostMapping(value = "/EntityDescriptor", consumes = "application/xml")
@Transactional
public ResponseEntity<?> upload(@RequestBody byte[] entityDescriptorXml, @RequestParam String spName) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package edu.internet2.tier.shibboleth.admin.ui.repository;

import java.time.LocalDateTime;

public interface EntityDescriptorProjection {
default String getId() {
return getResourceId();
}
String getEntityID();
default String getEntityId() {
return getEntityID();
}
String getResourceId();
String getServiceProviderName();
String getCreatedBy();
LocalDateTime getCreatedDate();
boolean getServiceEnabled();
String getIdOfOwner();
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
* Repository to manage {@link EntityDescriptor} instances.
*/
public interface EntityDescriptorRepository extends JpaRepository<EntityDescriptor, Long> {
List<EntityDescriptorProjection> findAllBy();

List<EntityDescriptorProjection> findAllByIdOfOwner(String ownerId);

EntityDescriptor findByEntityID(String entityId);

Expand All @@ -34,4 +37,4 @@ public interface EntityDescriptorRepository extends JpaRepository<EntityDescript
*/
@Deprecated
List<EntityDescriptor> findAllByIdOfOwnerIsNull();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package edu.internet2.tier.shibboleth.admin.ui.repository;

public interface ProjectionIdAndName{
public interface ProjectionIdAndName {
String getResourceId();

String getName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import edu.internet2.tier.shibboleth.admin.ui.domain.Attribute;
import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor;
import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation;
import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound;
import edu.internet2.tier.shibboleth.admin.ui.exception.ForbiddenException;
import edu.internet2.tier.shibboleth.admin.ui.exception.InvalidPatternMatchException;
import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException;
import edu.internet2.tier.shibboleth.admin.ui.exception.PersistentEntityNotFound;
import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection;

import java.util.ConcurrentModificationException;
import java.util.List;
Expand Down Expand Up @@ -67,9 +68,9 @@ EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRepres
Iterable<EntityDescriptorRepresentation> getAllDisabledAndNotOwnedByAdmin() throws ForbiddenException;

/**
* @return a list of EntityDescriptorRepresentations that a user has the rights to access
* @return a list of EntityDescriptorProjections that a user has the rights to access
*/
List<EntityDescriptorRepresentation> getAllRepresentationsBasedOnUserAccess() throws ForbiddenException;
List<EntityDescriptorProjection> getAllEntityDescriptorProjectionsBasedOnUserAccess() throws ForbiddenException;

/**
* Given a list of attributes, generate an AttributeReleaseList
Expand Down Expand Up @@ -119,4 +120,6 @@ EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String resour
EntityDescriptorRepresentation createNewEntityDescriptorFromXMLOrigin(EntityDescriptor ed);

boolean entityExists(String entityID);

EntityDescriptorRepresentation updateGroupForEntityDescriptor(String resourceId, String groupId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import edu.internet2.tier.shibboleth.admin.ui.exception.InvalidPatternMatchException;
import edu.internet2.tier.shibboleth.admin.ui.exception.ObjectIdExistsException;
import edu.internet2.tier.shibboleth.admin.ui.opensaml.OpenSamlObjects;
import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorProjection;
import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository;
import edu.internet2.tier.shibboleth.admin.ui.security.model.Group;
import edu.internet2.tier.shibboleth.admin.ui.security.model.Owner;
Expand Down Expand Up @@ -118,6 +119,14 @@ public boolean entityExists(String entityID) {
return entityDescriptorRepository.findByEntityID(entityID) != null ;
}

@Override
public EntityDescriptorRepresentation updateGroupForEntityDescriptor(String resourceId, String groupId) {
EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId);
ed.setIdOfOwner(groupId);
EntityDescriptor savedEntity = entityDescriptorRepository.save(ed);
return createRepresentationFromDescriptor(savedEntity);
}

@Override
public EntityDescriptorRepresentation createNew(EntityDescriptorRepresentation edRep)
throws ForbiddenException, ObjectIdExistsException, InvalidPatternMatchException {
Expand Down Expand Up @@ -373,16 +382,16 @@ public Iterable<EntityDescriptorRepresentation> getAllDisabledAndNotOwnedByAdmin
}

@Override
public List<EntityDescriptorRepresentation> getAllRepresentationsBasedOnUserAccess() throws ForbiddenException {
public List<EntityDescriptorProjection> getAllEntityDescriptorProjectionsBasedOnUserAccess() throws ForbiddenException {
switch (userService.getCurrentUserAccess()) {
case ADMIN:
return entityDescriptorRepository.findAllStreamByCustomQuery().map(ed -> createRepresentationFromDescriptor(ed))
.collect(Collectors.toList());
List<EntityDescriptorProjection> o = entityDescriptorRepository.findAllBy();
return o;
case GROUP:
User user = userService.getCurrentUser();
Group group = user.getGroup();
return entityDescriptorRepository.findAllStreamByIdOfOwner(group.getOwnerId())
.map(ed -> createRepresentationFromDescriptor(ed)).collect(Collectors.toList());
List<EntityDescriptorProjection> ed = entityDescriptorRepository.findAllByIdOfOwner(group.getOwnerId());
return ed;
default:
throw new ForbiddenException();
}
Expand Down
4 changes: 2 additions & 2 deletions ui/src/app/core/components/Spinner.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';

export function Spinner ({ size, className }) {
return (<FontAwesomeIcon icon={faSpinner} size={size} spin={true} pulse={true} className={ className } />)
export function Spinner (props) {
return (<FontAwesomeIcon icon={faSpinner} spin={true} pulse={true} { ...props } />)
}

export default Spinner;
2 changes: 1 addition & 1 deletion ui/src/app/dashboard/view/SourcesTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function SourcesTab () {
React.useEffect(() => { loadSources() }, []);

async function changeSourceGroup(source, group) {
await updater.put(`/${source.id}`, {
await updater.put(`/${source.id}/changeGroup/${group}`, {
...source,
idOfOwner: group
});
Expand Down
30 changes: 19 additions & 11 deletions ui/src/app/metadata/copy/CopySource.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function CopySource({ copy, onNext }) {
setSelected([]);
};

const { register, handleSubmit, control, formState, setValue, getValues } = useForm({
const { register, handleSubmit, control, formState, setValue, getValues, watch } = useForm({
mode: 'onChange',
reValidateMode: 'onBlur',
defaultValues: {
Expand All @@ -47,6 +47,8 @@ export function CopySource({ copy, onNext }) {
shouldUnregister: false,
});

const target = watch('target');

const { errors, isValid } = formState;

React.useEffect(() => {
Expand Down Expand Up @@ -92,38 +94,44 @@ export function CopySource({ copy, onNext }) {
<div className="col col-xs-12 col-xl-6">
<form onSubmit={handleSubmit(onNext)}>
<fieldset className="bg-light border rounded p-4">
<Form.Group className={`${errors.target ? 'is-invalid text-danger' : ''}`}>
<Form.Label htmlFor="target">
<Translate value="label.select-entity-id-to-copy">Select the Entity ID to copy</Translate>
<FontAwesomeIcon icon={faAsterisk} className="text-danger" />
</Form.Label>
<EntityTypeahead id="target" name="target" control={control} />
<Form.Group className={`mb-3 ${errors.target ? 'is-invalid text-danger' : ''}`}>
<EntityTypeahead id="target" name="target" control={control}>
<span>
<Translate value="label.select-entity-id-to-copy">Select the Entity ID to copy</Translate>
<FontAwesomeIcon icon={faAsterisk} className="text-danger ms-2" />
</span>
</EntityTypeahead>
<Form.Text id="target-help"
className={`text-danger ${errors?.target?.type === 'required' ? '' : 'sr-only'}`}>
<Translate value="message.target-required">Entity ID to copy is Required</Translate>
</Form.Text>
</Form.Group>
<Form.Group className={`${errors.serviceProviderName ? 'text-danger is-invalid' : ''}`}>
<Form.Group className={`mb-3 ${errors.serviceProviderName ? 'text-danger is-invalid' : ''}`}>
<Form.Label htmlFor="serviceProviderName">
<span>
<Translate value="label.metadata-source-name-dashboard-display-only">Metadata Source Name (Dashboard Display Only)</Translate>
<FontAwesomeIcon icon={faAsterisk} className="text-danger" />
<FontAwesomeIcon icon={faAsterisk} className="text-danger ms-2" />
</span>
</Form.Label>
<Form.Control id="serviceProviderName" type="text" className="form-control"
{...register('serviceProviderName', {required: true})}
aria-describedby="serviceProviderName-help" />
aria-describedby="serviceProviderName-help" disabled={!target} />
<Form.Text className={`form-text text-danger ${errors?.serviceProviderName?.type === 'required' ? '' : 'sr-only'}`}
id="serviceProviderName-help">
<Translate value="message.service-resolver-name-required">Service Resolver Name is required</Translate>
</Form.Text>
</Form.Group>
<Form.Group className={`${errors.entityId ? 'is-invalid text-danger' : ''}`}>
<Form.Label htmlFor="entityId">
<span>
<Translate value="label.service-resolver-entity-id">New Entity ID</Translate>
<FontAwesomeIcon icon={faAsterisk} className="text-danger" />
<FontAwesomeIcon icon={faAsterisk} className="text-danger ms-2" />
</span>
</Form.Label>
<Form.Control id="entityId" type="text"
isInvalid={errors.entityId}
aria-describedby="entityId-help"
disabled={!target}
{...register('entityId', {
required: true, validate: {
unique: v => !(sourceIds.indexOf(v) > -1)
Expand Down
42 changes: 32 additions & 10 deletions ui/src/app/metadata/copy/EntityTypeahead.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import React from 'react';
import { Typeahead } from 'react-bootstrap-typeahead';
import { useController } from 'react-hook-form';
import { useMetadataSources } from '../hooks/api';
import { useMetadataEntity, useMetadataSources } from '../hooks/api';
import Form from 'react-bootstrap/Form';

export function EntityTypeahead ({id, control, name}) {
export function EntityTypeahead ({id, control, name, children}) {
const { data = [] } = useMetadataSources({}, []);

const { get, response, loading } = useMetadataEntity();

const entities = React.useMemo(() => data.map(d => d.entityId), [data]);

const {
Expand All @@ -18,14 +24,30 @@ export function EntityTypeahead ({id, control, name}) {
defaultValue: "",
});

const loadSelected = async (selected) => {
const toCopy = data.find(e => e.entityId === selected);

const source = await get(`/${toCopy.id}`);
if (response.ok) {
onChange(source);
}
//
}

return (
<Typeahead
{...inputProps}
onChange={(selected) => onChange(selected ? data.find(e => e.entityId === selected[0]) : '')}
defaultInputValue={value ? value.entityId : ''}
options={entities}
required={true}
id={id}
/>
<React.Fragment>
<Form.Label htmlFor="target">
{children}
{loading && <FontAwesomeIcon icon={faSpinner} size="lg" spin={true} pulse={true} className="ms-2" /> }
</Form.Label>
<Typeahead
{...inputProps}
onChange={(selected) => selected && selected.length > 0 ? loadSelected(selected[0]) : null}
defaultInputValue={value ? value.entityId : ''}
options={entities}
required={true}
id={id}
/>
</React.Fragment>
)
}
28 changes: 20 additions & 8 deletions ui/src/app/metadata/hoc/MetadataSelector.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { useLocation, useParams } from 'react-router';
import Spinner from '../../core/components/Spinner';
import { useMetadataEntity } from '../hooks/api';

export const MetadataTypeContext = React.createContext();
Expand All @@ -10,6 +11,7 @@ export const MetadataLoaderContext = React.createContext();
export function MetadataSelector({ children, ...props }) {

let { type, id } = useParams();
const [loading, setLoading] = React.useState(false);
const location = useLocation();

if (!type) {
Expand All @@ -30,25 +32,35 @@ export function MetadataSelector({ children, ...props }) {
const source = await get(`/${id}`);
if (response.ok) {
setMetadata(source);
setLoading(false);
}
}

function reload() {
setLoading(true);
loadMetadata(id);

}

React.useEffect(() => reload(), [id]);

return (
<MetadataLoaderContext.Provider value={ reload }>
{type &&
<MetadataTypeContext.Provider value={type}>
{metadata && metadata.version &&
<MetadataObjectContext.Provider value={metadata}>{children(metadata, reload)}</MetadataObjectContext.Provider>
<React.Fragment>
{loading && <div className="d-flex justify-content-center text-primary">
<Spinner size="4x" className="m-4" />
</div> }
<MetadataLoaderContext.Provider value={ reload }>
{type &&
<MetadataTypeContext.Provider value={type}>
{metadata && metadata.version &&
<MetadataObjectContext.Provider value={metadata}>
{children(metadata, reload)}
</MetadataObjectContext.Provider>
}
</MetadataTypeContext.Provider>
}
</MetadataTypeContext.Provider>
}
</MetadataLoaderContext.Provider>
</MetadataLoaderContext.Provider>
</React.Fragment>
);
}

Expand Down

0 comments on commit 4ff0410

Please sign in to comment.