/** * */ package com.thinkbiganalytics.metadata.modeshape.security.role; /*- * #%L * kylo-metadata-modeshape * %% * Copyright (C) 2017 ThinkBig Analytics * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import java.security.Principal; import java.util.Arrays; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.jcr.Node; import com.thinkbiganalytics.metadata.api.security.RoleMembership; import com.thinkbiganalytics.metadata.modeshape.common.JcrObject; import com.thinkbiganalytics.metadata.modeshape.security.action.JcrAllowedActions; import com.thinkbiganalytics.metadata.modeshape.support.JcrPropertyUtil; import com.thinkbiganalytics.metadata.modeshape.support.JcrUtil; import com.thinkbiganalytics.security.GroupPrincipal; import com.thinkbiganalytics.security.UsernamePrincipal; import com.thinkbiganalytics.security.action.Action; import com.thinkbiganalytics.security.action.AllowableAction; import com.thinkbiganalytics.security.role.SecurityRole; /** * */ public class JcrRoleMembership extends JcrObject implements RoleMembership { private static final UsernamePrincipal[] NO_USERS = new UsernamePrincipal[0]; private static final GroupPrincipal[] NO_GROUPS = new GroupPrincipal[0]; public static final String NODE_NAME = "tba:roleMemberships"; public static final String NODE_TYPE = "tba:roleMembership"; public static final String ROLE = "tba:role"; public static final String GROUPS = "tba:groups"; public static final String USERS = "tba:users"; private transient JcrAllowedActions allowedActions; public static JcrRoleMembership create(Node parentNode, Node roleNode, JcrAllowedActions allowed) { // This is a no-op if the membership already exists in the SNS nodes return JcrUtil.getNodeList(parentNode, NODE_NAME).stream() .map(node -> JcrUtil.getJcrObject(node, JcrRoleMembership.class, allowed)) .filter(memshp -> memshp.getRole().getSystemName().equals(JcrUtil.getName(roleNode))) .findFirst() .orElseGet(() -> { return JcrUtil.addJcrObject(parentNode, NODE_NAME, NODE_TYPE, JcrRoleMembership.class, roleNode, allowed); }); } public static void remove(Node parentNode, Node roleNode) { JcrUtil.getNodeList(parentNode, NODE_NAME).forEach(node -> { JcrRoleMembership membership = JcrUtil.getJcrObject(node, JcrRoleMembership.class, (JcrAllowedActions) null); if (membership.getRole().getSystemName().equals(JcrUtil.getName(roleNode))) { JcrUtil.removeNode(node); } }); } public static void removeAll(Node parentNode) { JcrUtil.getNodeList(parentNode, NODE_NAME).forEach(node -> JcrUtil.removeNode(node)); } public static Optional<JcrRoleMembership> find(Node parentNode, String roleName, JcrAllowedActions allowed) { return JcrUtil.getNodeList(parentNode, NODE_NAME).stream() .map(node -> JcrUtil.getJcrObject(node, JcrRoleMembership.class, allowed)) .filter(memshp -> memshp.getRole().getSystemName().equals(roleName)) .findFirst(); } /** * Wraps a parent node containing the tba:accessControlled mixin. * @param node the parent entity node */ public JcrRoleMembership(Node node, JcrAllowedActions allowed) { super(node); this.allowedActions = allowed; } /** * Wraps a parent node containing the tba:accessControlled mixin. * @param node the parent entity node */ public JcrRoleMembership(Node node, Node roleNode, JcrAllowedActions allowed) { super(node); JcrPropertyUtil.setProperty(node, ROLE, roleNode); this.allowedActions = allowed; } /* (non-Javadoc) * @see com.thinkbiganalytics.metadata.api.security.RoleMembership#getRole() */ @Override public SecurityRole getRole() { return JcrUtil.getReferencedObject(getNode(), ROLE, JcrSecurityRole.class); } /* (non-Javadoc) * @see com.thinkbiganalytics.metadata.api.security.RoleMembership#getMembers() */ @Override public Set<Principal> getMembers() { Stream<? extends Principal> groups = streamGroups(); Stream<? extends Principal> users = streamUsers(); return Stream.concat(groups, users).collect(Collectors.toSet()); } /* (non-Javadoc) * @see com.thinkbiganalytics.metadata.api.security.RoleMembership#setMemebers(com.thinkbiganalytics.security.UsernamePrincipal[]) */ @Override public void setMemebers(UsernamePrincipal... principals) { Set<UsernamePrincipal> newMembers = Arrays.stream(principals).collect(Collectors.toSet()); Set<UsernamePrincipal> oldMembers = streamUsers().collect(Collectors.toSet()); newMembers.stream() .filter(u -> ! oldMembers.contains(u)) .forEach(this::addMember); oldMembers.stream() .filter(u -> ! newMembers.contains(u)) .forEach(this::removeMember); } /* (non-Javadoc) * @see com.thinkbiganalytics.metadata.api.security.RoleMembership#setMemebers(com.thinkbiganalytics.security.GroupPrincipal[]) */ @Override public void setMemebers(GroupPrincipal... principals) { Set<GroupPrincipal> newMembers = Arrays.stream(principals).collect(Collectors.toSet()); Set<GroupPrincipal> oldMembers = streamGroups().collect(Collectors.toSet()); newMembers.stream() .filter(u -> ! oldMembers.contains(u)) .forEach(this::addMember); oldMembers.stream() .filter(u -> ! newMembers.contains(u)) .forEach(this::removeMember); } /* (non-Javadoc) * @see com.thinkbiganalytics.metadata.api.security.RoleMembership#addMember(java.security.Principal) */ @Override public void addMember(GroupPrincipal principal) { JcrPropertyUtil.addToSetProperty(getNode(), GROUPS, principal.getName()); enable(principal); } /* (non-Javadoc) * @see com.thinkbiganalytics.metadata.api.security.RoleMembership#addMember(com.thinkbiganalytics.security.UsernamePrincipal) */ @Override public void addMember(UsernamePrincipal principal) { JcrPropertyUtil.addToSetProperty(getNode(), USERS, principal.getName()); enable(principal); } /* (non-Javadoc) * @see com.thinkbiganalytics.metadata.api.security.RoleMembership#removeMember(java.security.Principal) */ @Override public void removeMember(GroupPrincipal principal) { disable(principal); JcrPropertyUtil.removeFromSetProperty(getNode(), GROUPS, principal.getName()); } /* (non-Javadoc) * @see com.thinkbiganalytics.metadata.api.security.RoleMembership#removeMember(com.thinkbiganalytics.security.UsernamePrincipal) */ @Override public void removeMember(UsernamePrincipal principal) { disable(principal); JcrPropertyUtil.removeFromSetProperty(getNode(), USERS, principal.getName()); } @Override public void removeAllMembers() { setMemebers(NO_USERS); setMemebers(NO_GROUPS); } protected void enable(Principal principal) { JcrAllowedActions roleAllowed = (JcrAllowedActions) getRole().getAllowedActions(); Set<Action> actions = roleAllowed.getAvailableActions().stream() .flatMap(avail -> avail.stream()) .collect(Collectors.toSet()); this.allowedActions.enable(principal, actions); } protected void disable(Principal principal) { SecurityRole thisRole = getRole(); // Get all actions allowed by all other memberships of this principal besides this one. Node parentNode = JcrUtil.getParent(getNode()); Set<AllowableAction> otherAllowables = JcrUtil.getNodeList(parentNode, NODE_NAME).stream() .map(node -> JcrUtil.getJcrObject(node, JcrRoleMembership.class, (JcrAllowedActions) null)) .filter(membership -> membership.getMembers().contains(principal)) .map(membership -> membership.getRole()) .filter(role -> ! role.getSystemName().equals(thisRole.getSystemName())) .flatMap(role -> role.getAllowedActions().getAvailableActions().stream()) .flatMap(avail -> avail.stream()) .collect(Collectors.toSet()); // Disable only the actions not permitted by any other role memberships for this principal Set<Action> disabled = thisRole.getAllowedActions().getAvailableActions().stream() .flatMap(avail -> avail.stream()) .filter(action -> ! otherAllowables.contains(action)) .collect(Collectors.toSet()); this.allowedActions.disable(principal, disabled); } private Stream<UsernamePrincipal> streamUsers() { return JcrPropertyUtil.<String>getSetProperty(getNode(), USERS).stream().map(UsernamePrincipal::new); } private Stream<GroupPrincipal> streamGroups() { return JcrPropertyUtil.<String>getSetProperty(getNode(), GROUPS).stream().map(GroupPrincipal::new); } }