Skip to content

Commit

Permalink
Merged in work/shibui-2001 (pull request #510)
Browse files Browse the repository at this point in the history
Work/shibui 2001
  • Loading branch information
chasegawa committed Aug 16, 2021
2 parents 4de827b + 0b12ec9 commit 6574cc2
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,7 @@ class JPAMetadataResolverServiceImpl implements MetadataResolverService {
}

public edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver updateMetadataResolverEnabledStatus(edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver updatedResolver) throws ForbiddenException, MetadataFileNotFoundException, InitializationException {
// @TODO: when merged with groups, this should maybe be merged with group check as they have to have the role in the right group
if (!userService.currentUserHasExpectedRole(["ROLE_ADMIN", "ROLE_ENABLE"])) {
if (!userService.currentUserCanEnable(updatedResolver)) {
throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this filter.")
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package edu.internet2.tier.shibboleth.admin.ui.domain;

public enum ActivatableType {
ENTITY_DESCRIPTOR, METADATA_RESOLVER, FILTER
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@
import java.util.UUID;
import java.util.stream.Collectors;

import static edu.internet2.tier.shibboleth.admin.ui.domain.ActivatableType.ENTITY_DESCRIPTOR;

@Entity
@EqualsAndHashCode(callSuper = true)
@Audited
public class EntityDescriptor extends AbstractDescriptor implements org.opensaml.saml.saml2.metadata.EntityDescriptor, Ownable {
public class EntityDescriptor extends AbstractDescriptor implements org.opensaml.saml.saml2.metadata.EntityDescriptor, Ownable, IActivatable {
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "entitydesc_addlmetdatlocations_id")
@OrderColumn
Expand All @@ -47,7 +48,7 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml
@OneToOne(cascade = CascadeType.ALL)
@NotAudited
private AffiliationDescriptor affiliationDescriptor;

@OneToOne(cascade = CascadeType.ALL)
@NotAudited
private AttributeAuthorityDescriptor attributeAuthorityDescriptor;
Expand All @@ -61,7 +62,7 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml
private List<ContactPerson> contactPersons = new ArrayList<>();

private String entityID;

private String localId;

@OneToOne(cascade = CascadeType.ALL)
Expand All @@ -70,7 +71,7 @@ public class EntityDescriptor extends AbstractDescriptor implements org.opensaml
@Getter
@Setter
private String idOfOwner;

@OneToOne(cascade = CascadeType.ALL)
@NotAudited
private PDPDescriptor pdpDescriptor;
Expand Down Expand Up @@ -254,6 +255,10 @@ public void setEntityID(String entityID) {
this.entityID = entityID;
}

public void setEnabled(Boolean serviceEnabled) {
this.serviceEnabled = (serviceEnabled == null) ? false : serviceEnabled;
}

@Override
public void setID(String id) {
this.localId = id;
Expand Down Expand Up @@ -296,12 +301,16 @@ public String toString() {
.add("id", id)
.toString();
}

public String getObjectId() {
return entityID;
}

public OwnableType getOwnableType() {
return OwnableType.ENTITY_DESCRIPTOR;
}
}

@Override public ActivatableType getActivatableType() {
return ENTITY_DESCRIPTOR;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package edu.internet2.tier.shibboleth.admin.ui.domain;

public interface IActivatable {
ActivatableType getActivatableType();

void setEnabled(Boolean enabled);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package edu.internet2.tier.shibboleth.admin.ui.domain.filters;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.*;
import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable;
import edu.internet2.tier.shibboleth.admin.ui.domain.ActivatableType;
import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -20,6 +19,8 @@
import javax.persistence.Transient;
import java.util.UUID;

import static edu.internet2.tier.shibboleth.admin.ui.domain.ActivatableType.*;

/**
* Domain class to store information about {@link org.opensaml.saml.metadata.resolver.filter.MetadataFilter}
*/
Expand All @@ -38,27 +39,36 @@
@JsonSubTypes.Type(value=NameIdFormatFilter.class, name="NameIDFormat")})
@Audited
@AuditOverride(forClass = AbstractAuditable.class)
public abstract class MetadataFilter extends AbstractAuditable implements IConcreteMetadataFilterType<MetadataFilter> {
public abstract class MetadataFilter extends AbstractAuditable implements IConcreteMetadataFilterType<MetadataFilter>, IActivatable {

@JsonProperty("@type")
@Transient
String type;
private boolean filterEnabled;

private String name;

@Column(unique=true)
private String resourceId = UUID.randomUUID().toString();

private boolean filterEnabled;
@JsonProperty("@type")
@Transient
String type;

@Transient
private transient Integer version;

@JsonIgnore
public ActivatableType getActivatableType() {
return FILTER;
}

@JsonGetter("version")
public int getVersion() {
if (version != null && version != 0) {
return this.version;
}
return this.hashCode();
}
}

public void setEnabled(Boolean serviceEnabled) {
this.filterEnabled = (serviceEnabled == null) ? false : serviceEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import edu.internet2.tier.shibboleth.admin.ui.domain.AbstractAuditable;
import edu.internet2.tier.shibboleth.admin.ui.domain.ActivatableType;
import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter;
import lombok.EqualsAndHashCode;
Expand All @@ -28,6 +30,8 @@
import java.util.List;
import java.util.UUID;

import static edu.internet2.tier.shibboleth.admin.ui.domain.ActivatableType.*;

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@EqualsAndHashCode(callSuper = true, exclude = {"version", "versionModifiedTimestamp"})
Expand All @@ -43,7 +47,7 @@
@JsonSubTypes.Type(value = ResourceBackedMetadataResolver.class, name = "ResourceBackedMetadataResolver")})
@Audited
@AuditOverride(forClass = AbstractAuditable.class)
public class MetadataResolver extends AbstractAuditable {
public class MetadataResolver extends AbstractAuditable implements IActivatable {

@JsonProperty("@type")
@Transient
Expand Down Expand Up @@ -84,31 +88,37 @@ public class MetadataResolver extends AbstractAuditable {
@Transient
private Integer version;

@JsonGetter("version")
public int getVersion() {
if (this.version != null && this.version != 0 ) {
return this.version;
}
return this.hashCode();
}

public void addFilter(MetadataFilter metadataFilter) {
//To make sure that Spring Data auditing infrastructure recognizes update and "touched" modifiedDate
markAsModified();
this.metadataFilters.add(metadataFilter);
}

public void markAsModified() {
this.versionModifiedTimestamp = System.currentTimeMillis();
}

public void entityAttributesFilterIntoTransientRepresentation() {
//expose explicit API to call to convert into transient representation
//used in unit/integration tests where JPA's @PostLoad callback execution engine is not available
this.metadataFilters
.stream()
.filter(EntityAttributesFilter.class::isInstance)
.map(EntityAttributesFilter.class::cast)
.forEach(EntityAttributesFilter::intoTransientRepresentation);
.stream()
.filter(EntityAttributesFilter.class::isInstance)
.map(EntityAttributesFilter.class::cast)
.forEach(EntityAttributesFilter::intoTransientRepresentation);
}

@Override
@JsonIgnore
public ActivatableType getActivatableType() {
return METADATA_RESOLVER;
}

@JsonGetter("version")
public int getVersion() {
if (this.version != null && this.version != 0 ) {
return this.version;
}
return this.hashCode();
}

public void markAsModified() {
this.versionModifiedTimestamp = System.currentTimeMillis();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package edu.internet2.tier.shibboleth.admin.ui.security.service;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.*;

import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor;
import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -25,6 +25,8 @@
import edu.internet2.tier.shibboleth.admin.ui.security.repository.UserRepository;
import lombok.NoArgsConstructor;

import static edu.internet2.tier.shibboleth.admin.ui.security.service.UserAccess.*;

@Service
@NoArgsConstructor
public class UserService {
Expand All @@ -47,10 +49,25 @@ public UserService(IGroupService groupService, OwnershipRepository ownershipRepo
this.userRepository = userRepository;
}

public boolean currentUserCanEnable(IActivatable activatableObject) {
if (currentUserIsAdmin()) { return true; }
switch (activatableObject.getActivatableType()) {
case ENTITY_DESCRIPTOR: {
return currentUserHasExpectedRole(Arrays.asList("ROLE_ENABLE" )) && getCurrentUserGroup().getOwnerId().equals(((EntityDescriptor) activatableObject).getIdOfOwner());
}
// Currently filters and providers dont have ownership, so we just look for the right role
case FILTER:
case METADATA_RESOLVER:
return currentUserHasExpectedRole(Arrays.asList("ROLE_ENABLE" ));
default:
return false;
}
}

/**
* Current logic is pretty dumb, this will need to change/expand once a user can have more than one role.
* This basic logic assumes users only have a single role (despite users having a list of roles, we assume only 1 currently)
*/
public boolean currentUserHasExpectedRole(List<String> acceptedRoles) {
private boolean currentUserHasExpectedRole(List<String> acceptedRoles) {
User user = getCurrentUser();
return acceptedRoles.contains(user.getRole());
}
Expand Down Expand Up @@ -91,15 +108,15 @@ public User getCurrentUser() {
public UserAccess getCurrentUserAccess() {
User user = getCurrentUser();
if (user == null) {
return UserAccess.NONE;
return NONE;
}
if (user.getRole().equals("ROLE_ADMIN")) {
return UserAccess.ADMIN;
return ADMIN;
}
if (user.getRole().equals("ROLE_USER")) {
return UserAccess.GROUP;
return GROUP;
}
return UserAccess.NONE;
return NONE;
}

public Group getCurrentUserGroup() {
Expand All @@ -120,6 +137,7 @@ public Set<String> getUserRoles(String username) {
return result;
}

// @TODO - probably delegate this out to something plugable at some point
public boolean isAuthorizedFor(Ownable ownableObject) {
switch (getCurrentUserAccess()) {
case ADMIN: // Pure admin is authorized to do anything
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ public EntityDescriptorRepresentation update(EntityDescriptorRepresentation edRe
if (existingEd == null) {
throw new EntityNotFoundException(String.format("The entity descriptor with entity id [%s] was not found for update.", edRep.getId()));
}
if (edRep.isServiceEnabled() && !userService.currentUserIsAdmin()) {
if (edRep.isServiceEnabled() && !userService.currentUserCanEnable(existingEd)) {
throw new ForbiddenException("You do not have the permissions necessary to enable this service.");
}
if (!userService.isAuthorizedFor(existingEd)) {
Expand Down Expand Up @@ -391,8 +391,7 @@ public EntityDescriptorRepresentation updateEntityDescriptorEnabledStatus(String
if (ed == null) {
throw new EntityNotFoundException("Entity with resourceid[" + resourceId + "] was not found for update");
}
// @TODO: when merged with groups, this should maybe be merged with group check as they have to have the role in the right group
if (!userService.currentUserHasExpectedRole(Arrays.asList(new String[] { "ROLE_ADMIN", "ROLE_ENABLE" }))) {
if (!userService.currentUserCanEnable(ed)) {
throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this entity descriptor.");
}
ed.setServiceEnabled(status);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.internet2.tier.shibboleth.admin.ui.service;

import edu.internet2.tier.shibboleth.admin.ui.domain.IActivatable;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.filters.MetadataFilter;
import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.FilterRepresentation;
Expand Down Expand Up @@ -117,13 +118,13 @@ public MetadataFilter updateFilterEnabledStatus(String metadataResolverId, Strin
if (filterTobeUpdatedOptional.isEmpty()) {
throw new EntityNotFoundException("Filter with resource id[" + resourceId + "] not found");
}

// @TODO: when merged with groups, this should maybe be merged with group check as they have to have the role in the right group
if (!userService.currentUserHasExpectedRole(Arrays.asList(new String[] { "ROLE_ADMIN", "ROLE_ENABLE" }))) {

MetadataFilter filterTobeUpdated = filterTobeUpdatedOptional.get();

if (!userService.currentUserCanEnable(filterTobeUpdated)) {
throw new ForbiddenException("You do not have the permissions necessary to change the enable status of this filter.");
}

MetadataFilter filterTobeUpdated = filterTobeUpdatedOptional.get();

filterTobeUpdated.setFilterEnabled(status);
MetadataFilter persistedFilter = filterRepository.save(filterTobeUpdated);

Expand All @@ -136,4 +137,4 @@ public MetadataFilter updateFilterEnabledStatus(String metadataResolverId, Strin

return persistedFilter;
}
}
}
4 changes: 2 additions & 2 deletions backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ shibui.mail.text-email-template-path-prefix=/mail/text/
shibui.mail.html.email-template-path-prefix=/mail/html/
shibui.mail.system-email-address=doNotReply@shibui.org

shibui.roles=ROLE_ADMIN,ROLE_USER,ROLE_NONE
shibui.roles=ROLE_ADMIN,ROLE_ENABLE,ROLE_USER,ROLE_NONE

#In order to enable authentication via configured pac4j library (with external SAMl Idp, for example)
#This property must be set to true and pac4j properties configured. For sample pac4j properties, see application.yml
Expand All @@ -97,4 +97,4 @@ shibui.roles=ROLE_ADMIN,ROLE_USER,ROLE_NONE
#This property must be set to true in order to enable posting stats to beacon endpoint. Furthermore, appropriate
#environment variables must be set for beacon publisher to be used (the ones that are set when running shib-ui in
#docker container
shibui.beacon-enabled=true
shibui.beacon-enabled=true
Loading

0 comments on commit 6574cc2

Please sign in to comment.