package de.passau.uni.sec.compose.id.core.service; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import javax.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import sun.security.acl.GroupImpl; import de.passau.uni.sec.compose.id.common.exception.IdManagementException; import de.passau.uni.sec.compose.id.common.exception.IdManagementException.Level; import de.passau.uni.sec.compose.id.core.domain.ComposeComponentPrincipal; import de.passau.uni.sec.compose.id.core.domain.ComposeUserPrincipal; import de.passau.uni.sec.compose.id.core.domain.IPrincipal; import de.passau.uni.sec.compose.id.core.event.ApproveEntityGroupMembershipEvent; import de.passau.uni.sec.compose.id.core.event.ApproveMembershipEvent; import de.passau.uni.sec.compose.id.core.event.CreateEntityGroupMembershipEvent; import de.passau.uni.sec.compose.id.core.event.CreateUserEvent; import de.passau.uni.sec.compose.id.core.event.CreateMembershipEvent; import de.passau.uni.sec.compose.id.core.event.Event; import de.passau.uni.sec.compose.id.core.event.GetUserEvent; import de.passau.uni.sec.compose.id.core.event.DetailsIdEvent; import de.passau.uni.sec.compose.id.core.event.UpdateUserEvent; import de.passau.uni.sec.compose.id.core.persistence.entities.Application; import de.passau.uni.sec.compose.id.core.persistence.entities.CoreEntity; import de.passau.uni.sec.compose.id.core.persistence.entities.EntityGroupMembership; import de.passau.uni.sec.compose.id.core.persistence.entities.Group; import de.passau.uni.sec.compose.id.core.persistence.entities.IEntity; import de.passau.uni.sec.compose.id.core.persistence.entities.Membership; import de.passau.uni.sec.compose.id.core.persistence.entities.Role; import de.passau.uni.sec.compose.id.core.persistence.entities.ServiceComposition; import de.passau.uni.sec.compose.id.core.persistence.entities.ServiceInstance; import de.passau.uni.sec.compose.id.core.persistence.entities.ServiceObject; import de.passau.uni.sec.compose.id.core.persistence.entities.ServiceSourceCode; import de.passau.uni.sec.compose.id.core.persistence.entities.User; import de.passau.uni.sec.compose.id.core.persistence.repository.ApplicationRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.EntityGroupMembershipRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.GroupRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.MembershipRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.RoleRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.ServiceCompositionRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.ServiceInstanceRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.ServiceObjectRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.ServiceSourceCodeRepository; import de.passau.uni.sec.compose.id.core.persistence.repository.UserRepository; import de.passau.uni.sec.compose.id.core.service.reputation.ReputationManager; import de.passau.uni.sec.compose.id.core.service.security.RestAuthentication; import de.passau.uni.sec.compose.id.core.service.security.UsersAuthzAndAuthClient; import de.passau.uni.sec.compose.id.core.service.security.uaa.OpenIdUserData; import de.passau.uni.sec.compose.id.core.service.security.uaa.UAAUserRequest; import de.passau.uni.sec.compose.id.core.service.security.uaa.UAAUserRequestName; import de.passau.uni.sec.compose.id.rest.controller.UserCommandsController; import de.passau.uni.sec.compose.id.rest.messages.EntityGroupMembershipResponseMessage; import de.passau.uni.sec.compose.id.rest.messages.EntityResponseMessage; import de.passau.uni.sec.compose.id.rest.messages.MembershipResponseMessage; import de.passau.uni.sec.compose.id.rest.messages.PendingUserMembershipMessage; import de.passau.uni.sec.compose.id.rest.messages.UserResponseMessage; @Service public class EntityGroupMembershipService extends AbstractBasicListEntityService implements EntityService { private static Logger LOG = LoggerFactory.getLogger(EntityGroupMembershipService.class); @Autowired GroupRepository groupRepository; @Autowired EntityGroupMembershipRepository membershipRepository; @Autowired UserRepository userRepository; @Autowired ApplicationRepository appRepository; @Autowired ServiceObjectRepository serviceObjectRepository; @Autowired ServiceInstanceRepository serviceInstanceRepository; @Autowired ServiceCompositionRepository serviceCompositionRepository; @Autowired ServiceSourceCodeRepository serviceSourceCodeRepository; @Autowired RestAuthentication authentication; @Autowired UpdateManager updater; private EntityGroupMembership buildEntityGroupMembership(User u, CreateEntityGroupMembershipEvent event) throws IdManagementException { CoreEntity entity = getEntity(event); EntityGroupMembership memb = new EntityGroupMembership(); if(entity instanceof Application) memb.setApplication(appRepository.getOne(event.getEntityId())); else if(entity instanceof ServiceComposition) memb.setServiceComposition(serviceCompositionRepository.getOne(event.getEntityId())); else if(entity instanceof ServiceInstance) memb.setServiceInstance(serviceInstanceRepository.getOne(event.getEntityId())); else if(entity instanceof ServiceObject) memb.setServiceObject(serviceObjectRepository.getOne(event.getEntityId())); else if(entity instanceof ServiceSourceCode) memb.setServiceSourceCode(serviceSourceCodeRepository.getOne(event.getEntityId())); else throw new IdManagementException("The entity type is not valid",null, LOG,"Principal attempting to create a entity group membership with a wrong entity type"+event.getEntityType()+". princpals: "+RestAuthentication.getBasicInfoPrincipals(event.getPrincipals()),Level.ERROR, 404); return memb; } /** * * @param event Event for creation of the entity * @return The entity corresponding to the event * @throws IdManagementException exception if the type of entity is not found, or if the proper repository failed to find the identity for the given id. */ private CoreEntity getEntity(CreateEntityGroupMembershipEvent event) throws IdManagementException { CoreEntity ret = null; if(event.getEntityType().equals(EntityGroupMembership.APPLICATION)) ret = appRepository.getOne(event.getEntityId()); else if(event.getEntityType().equals(EntityGroupMembership.SERVICECOMPOSITION)) ret = serviceCompositionRepository.getOne(event.getEntityId()); else if(event.getEntityType().equals(EntityGroupMembership.SERVICEINSTANCE)) ret = serviceInstanceRepository.getOne(event.getEntityId()); else if(event.getEntityType().equals(EntityGroupMembership.SERVICEOBJECT)) ret =serviceObjectRepository.getOne(event.getEntityId()); else if(event.getEntityType().equals(EntityGroupMembership.SERVICESOURCECODE)) ret =serviceSourceCodeRepository.getOne(event.getEntityId()); else throw new IdManagementException("The entity type is not valid",null, LOG,"Principal attempting to create a entity group membership with a wrong entity type"+event.getEntityType()+". princpals: "+RestAuthentication.getBasicInfoPrincipals(event.getPrincipals()),Level.ERROR, 404); return ret; } private EntityGroupMembership getMembershipForEntityInGroup(CoreEntity entity, Group group) { List<?> ret = null; if(entity instanceof Application) ret = membershipRepository.findByApplicationAndGroup((Application) entity, group); else if(entity instanceof ServiceComposition) ret = membershipRepository.findByServiceCompositionAndGroup((ServiceComposition) entity, group); else if(entity instanceof ServiceInstance) ret = membershipRepository.findByServiceInstanceAndGroup((ServiceInstance) entity, group); else if(entity instanceof ServiceObject) ret = membershipRepository.findByServiceObjectAndGroup((ServiceObject) entity, group); else if(entity instanceof ServiceSourceCode) ret = membershipRepository.findByServiceSourceCodeAndGroup((ServiceSourceCode) entity, group); if( ret!=null&&ret.size()>0) return (EntityGroupMembership) ret.iterator().next(); return null; } @Override protected void verifyAccessControlCreateEntity(Event event) throws IdManagementException { CoreEntity entity = getEntity((CreateEntityGroupMembershipEvent) event); CreateEntityGroupMembershipEvent membEvent = ((CreateEntityGroupMembershipEvent)event); Collection<IPrincipal> principals = event.getPrincipals(); for(IPrincipal p: principals) { if(p instanceof ComposeUserPrincipal) { ComposeUserPrincipal user = ((ComposeUserPrincipal)p); OpenIdUserData userData = user.getOpenId(); if(entity.getOwner().getId().equals(user.getOpenId().getUser_id())) membEvent.setExecutedByEntityOwner(true); User u = userRepository.getOne(userData.getUser_id()); if(u.isGroupAdmin(membEvent.getMessage().getGroup_id())) membEvent.setExecutedByGroupAdmin(true); Group g = groupRepository.getOne(membEvent.getMessage().getGroup_id()); if(g.getOwner().getId().equals(u.getId())) membEvent.setExecutedByGroupOwner(true); } } if(!membEvent.isExecutedByGroupAdmin() && !membEvent.isExecutedByGroupOwner() && !membEvent.isExecutedByEntityOwner()) throw new IdManagementException("Not sufficient permissions for the action requred : Attempt to add an entity in a group where you are not admin nor the owner",null, LOG,"Attempt to add another user in a group where the principal is not admin nor owner. The entities authenticated for the request do not have sufficient permissions to execute it, principals "+RestAuthentication.getBasicInfoPrincipals(principals),Level.ERROR, 403); } @Override protected EntityResponseMessage postACCreateEntity(Event event) throws IdManagementException { CreateEntityGroupMembershipEvent membEvent = ((CreateEntityGroupMembershipEvent)event); User u = authentication.getUserFromEvent(event); Group group =groupRepository.getOne(membEvent.getMessage().getGroup_id()); EntityGroupMembership m = buildEntityGroupMembership(u,membEvent); EntityGroupMembership um = getMembershipForEntityInGroup(m.getEntity(),group); if(um!=null) throw new IdManagementException("Entity with id:"+membEvent.getEntityId()+" with type:"+membEvent.getEntityType()+" has already a membership for the same group. It is waiting for approval from "+(um.isApprovedByGroupOwner()?" the owner of the entity":" the group owner/admin"),null, LOG, "Entity with id:"+membEvent.getEntityId()+" with type:" +membEvent.getEntityType()+" has already a membership for the same group. It is waiting for approval from " +(um.isApprovedByGroupOwner()?" the owner of the entity ":" the group owner/admin") +"Principals: "+RestAuthentication.getBasicInfoPrincipals(membEvent.getPrincipals()),Level.INFO, 409); m.setApprovedByGroupOwner(membEvent.isExecutedByGroupOwner()||membEvent.isExecutedByGroupAdmin()); m.setApprovedBySelfOwner(membEvent.isExecutedByEntityOwner()); m.setGroup(group); m.setId(UUID.randomUUID().toString()); membershipRepository.save(m); if(m.isApprovedByGroupOwner() && m.isApprovedBySelfOwner()) updater.handleUpdateForEntity(m.getEnityId(),event.getPrincipals()); return new EntityGroupMembershipResponseMessage(m); } @Override protected void verifyAccessControlGetEntity(Event event) throws IdManagementException { //TODO } @Override protected EntityResponseMessage postACGetEntity(Event event) throws IdManagementException { //TODO return null; } @Override protected void verifyAccessControlUpdateEntity(DetailsIdEvent event) throws IdManagementException { Collection<IPrincipal> principals = event.getPrincipals(); if(principals == null || principals.size()==0) throw new IdManagementException("Authentication required.",null, LOG," Attempt to list unapproved memberships without providing credentials",Level.DEBUG, 401); if(principals.size()!=1) throw new IdManagementException("Only one user principal should call this API endpoint.",null, LOG,"There is more than one principal for getting pending user approvals ",Level.DEBUG, 401); //The rest of checks need to be done with the update since it must be specifically noticed whether the update is being done by the user, or the group owner/admin... see postACUPdate method... } @Override protected EntityResponseMessage postACUpdateEntity(DetailsIdEvent event, IEntity previous) throws IdManagementException { Collection<IPrincipal> principals = event.getPrincipals(); ApproveEntityGroupMembershipEvent approve = ((ApproveEntityGroupMembershipEvent ) event); EntityGroupMembership attemptedMembership = membershipRepository.getOne(approve.getEntityId()); IPrincipal prin = principals.iterator().next(); boolean updated = false; if(prin instanceof ComposeUserPrincipal) { ComposeUserPrincipal userPrincipal = ((ComposeUserPrincipal)prin); CoreEntity entityInMembership = attemptedMembership.getEntity(); //it is a membership that needs authorization of the owner of the entity if(entityInMembership.getOwner().getId().equals(userPrincipal.getOpenId().getUser_id()) && !attemptedMembership.isApprovedBySelfOwner()) { updated = true; attemptedMembership.setApprovedBySelfOwner(true); } //owner of the group if(needsApprovalByUserAsOwner(attemptedMembership,userPrincipal)) { attemptedMembership.setApprovedByGroupOwner(true); updated=true; } if(needsApprovalByUserAsGroupAdmin(attemptedMembership, userPrincipal)) { attemptedMembership.setApprovedByGroupOwner(true); updated=true; } if(updated) updater.handleUpdateForEntity(attemptedMembership.getEnityId(),event.getPrincipals()); else //the principal is admin in the group -approved- and the attempted membership requires that group admin or owners approve it throw new IdManagementException("There was no pending approval from the principal calling the API",null, LOG,"Principal attempting to approve a entity group membership that he didn't have to approve. Membership group"+attemptedMembership.getGroup().getId() +"Entity involved in membership id: "+entityInMembership.getId()+", Entity id : "+attemptedMembership.getEnityId()+" Entity type:"+attemptedMembership.getEntityType()+". princpals: "+RestAuthentication.getBasicInfoPrincipals(event.getPrincipals()),Level.INFO, 403); } else throw new IdManagementException("To approve a membership, the principal must be a user",null, LOG,"Principal attempting to approve a membership is not a user. princpals: "+RestAuthentication.getBasicInfoPrincipals(event.getPrincipals()),Level.INFO, 401); attemptedMembership.setLastModified(new Date( (new Date().getTime()/1000)*1000 )); attemptedMembership = membershipRepository.save(attemptedMembership); return new EntityGroupMembershipResponseMessage(attemptedMembership); } @Override protected Logger getLogger() { return LOG; } @Override protected IEntity getEntityById(String entityId) { return membershipRepository.getOne(entityId); } @Override public Object postACListAllEntities(Event event) throws IdManagementException { //this one is a map to prevent returning repeated approvals that may appear if a user is agroup owner and an admin at the same time HashMap<String, EntityGroupMembershipResponseMessage> groupOwner = new HashMap<String, EntityGroupMembershipResponseMessage>() ; List<EntityGroupMembershipResponseMessage> selfApprovals = new LinkedList<EntityGroupMembershipResponseMessage>(); User u = authentication.getUserFromEvent(event); Collection<Group> groups = u.getGroups(); //entity memberships for groups owned by the user for(Group group: groups) for(EntityGroupMembership m: membershipRepository.findByGroup(group)) if(!m.isApprovedByGroupOwner()) groupOwner.put(m.getId(),new EntityGroupMembershipResponseMessage(m)); //entity memberships for groups where the user is admin for(Membership principaMembership: u.getMemberships()) { if(u.isGroupAdmin(principaMembership.getGroup().getId())) { List<EntityGroupMembership> pendingAdmin = membershipRepository.findByGroup(principaMembership.getGroup()); for(EntityGroupMembership pending: pendingAdmin) if(!pending.isApprovedByGroupOwner()) groupOwner.put(pending.getId(), new EntityGroupMembershipResponseMessage(pending)); } } List<EntityGroupMembershipResponseMessage> group = new LinkedList<EntityGroupMembershipResponseMessage>(); for(String key: groupOwner.keySet()) group.add(groupOwner.get(key)); selfApprovals = getEntityGroupMembershipsNonApprovedAsOwner(u); return new PendingUserMembershipMessage(group, selfApprovals); } private List<EntityGroupMembershipResponseMessage> getEntityGroupMembershipsNonApprovedAsOwner( User user) { List<EntityGroupMembership> membs= new LinkedList<>(); List<Application> apps=appRepository.findByOwner(user); List<ServiceInstance> sis=serviceInstanceRepository.findByOwner(user); List<ServiceObject> sos=serviceObjectRepository.findByOwner(user); List<ServiceComposition> scs=serviceCompositionRepository.findByOwner(user); List<ServiceSourceCode> sources=serviceSourceCodeRepository.findByDeveloper(user); for(Application app: apps) membs.addAll(membershipRepository.findByApplicationAndApprovedBySelfOwner(app, false)); for(ServiceInstance si: sis) membs.addAll(membershipRepository.findByServiceInstanceAndApprovedBySelfOwner(si, false)); for(ServiceObject so: sos) membs.addAll(membershipRepository.findByServiceObjectAndApprovedBySelfOwner(so, false)); for(ServiceComposition sc: scs) membs.addAll(membershipRepository.findByServiceCompositionAndApprovedBySelfOwner(sc, false)); for(ServiceSourceCode source: sources) membs.addAll(membershipRepository.findByServiceSourceCodeAndApprovedBySelfOwner(source, false)); List<EntityGroupMembershipResponseMessage> ret = new LinkedList<>(); for(EntityGroupMembership membership: membs) ret.add(new EntityGroupMembershipResponseMessage(membership)); return ret; } @Override public void verifyACListAllEntities(Event event) throws IdManagementException { //Just check that there is only one principal Collection<IPrincipal> principals = event.getPrincipals(); if(principals == null || principals.size()==0) throw new IdManagementException("Authentication required.",null, LOG," Attempt to list unapproved memberships without providing credentials",Level.DEBUG, 401); if(principals.size()!=1) throw new IdManagementException("Only one user principal should call this API endpoint.",null, LOG,"There is more than one principal for getting pending user approvals ",Level.DEBUG, 401); } @Override protected void postACDeleteEntity(DetailsIdEvent event) throws IdManagementException { EntityGroupMembership attempted = membershipRepository.getOne(event.getEntityId()); String entityId = attempted.getEnityId(); membershipRepository.delete(attempted); updater.handleUpdateForEntity(entityId,event.getPrincipals()); } @Override protected void verifyAccessControlDeleteEntity(DetailsIdEvent event) throws IdManagementException { Collection<IPrincipal> principals = event.getPrincipals(); if(principals == null || principals.size()==0) throw new IdManagementException("Authentication required.",null, LOG," Attempt to list unapproved memberships without providing credentials",Level.DEBUG, 401); if(principals.size()!=1) throw new IdManagementException("Only one user principal should call this API endpoint.",null, LOG,"There is more than one principal while trying to remove a user approval",Level.DEBUG, 401); EntityGroupMembership memb = membershipRepository.getOne(event.getEntityId()); IPrincipal prin = principals.iterator().next(); if(prin instanceof ComposeUserPrincipal) { if(!userCanDelete(memb, (ComposeUserPrincipal) prin)) throw new IdManagementException("User does not have sufficient permissions to delete the membesrhip ",null, LOG,"Principal attempting to delete a Entity group membership without sufficient permissions. Membership group"+memb.getGroup().getId() +" entity id: "+memb.getEnityId()+", entity type: "+memb.getEntityType()+". princpals: "+RestAuthentication.getBasicInfoPrincipals(event.getPrincipals()),Level.INFO, 403); } else throw new IdManagementException("To approve a membership, the principal must be a user",null, LOG,"Principal attempting to approve a membership is not a user. princpals: "+RestAuthentication.getBasicInfoPrincipals(event.getPrincipals()),Level.INFO, 401); } /** * * @param attemptedMembership membership that is attempting to be updated * @param userPrincipal principal executing the action * @return true if the membership requires the approval of the user as owner of the entity being added */ private boolean needsApprovalByUserAsOwner(EntityGroupMembership attemptedMembership, ComposeUserPrincipal userPrincipal) { boolean cond = ( userPrincipal.getOpenId().getUser_id().equals(attemptedMembership.getGroup().getOwner().getId()) && !attemptedMembership.isApprovedByGroupOwner()); return cond; } /** * * @param attemptedMembership membership that is attempting to be updated * @param userPrincipal principal executing the action * @return true if the membership requires the approval of the user as owner of the entity being added */ private boolean needsApprovalByUserAsGroupAdmin(EntityGroupMembership attemptedMembership, ComposeUserPrincipal userPrincipal) { User u = userRepository.getOne(userPrincipal.getOpenId().getUser_id()); return u.isGroupAdmin(attemptedMembership.getGroup().getId()) && !attemptedMembership.isApprovedByGroupOwner(); } private boolean userCanDelete(EntityGroupMembership membership, ComposeUserPrincipal user) { //check if the user is admin of the group of the membership User u = userRepository.getOne(user.getOpenId().getUser_id()); if(u.isGroupAdmin(membership.getGroup().getId())) return true; // if it was not an admin of the group, then verify if it is the group owner if ( user.getOpenId().getUser_id().equals(membership.getGroup().getOwner().getId()) ) return true; //if it was not any of the previous conditions, the last chance it is that the user owns the entity return membership.getEntity().getOwner().getId().equals(u.getId()); } }