Java tutorial
/* * The Gemma project * * Copyright (c) 2006 University of British Columbia * * 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. * */ package ubic.gemma.web.controller.common.auditAndSecurity; import gemma.gsec.AuthorityConstants; import gemma.gsec.SecurityService; import gemma.gsec.authentication.UserDetailsImpl; import gemma.gsec.authentication.UserManager; import gemma.gsec.model.Securable; import gemma.gsec.model.User; import gemma.gsec.util.SecurityUtil; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mail.SimpleMailMessage; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.acls.model.Sid; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; import ubic.gemma.core.genome.gene.service.GeneSetService; import ubic.gemma.model.analysis.expression.ExpressionExperimentSet; import ubic.gemma.model.analysis.expression.diff.GeneDifferentialExpressionMetaAnalysis; import ubic.gemma.model.association.phenotype.DifferentialExpressionEvidence; import ubic.gemma.model.association.phenotype.PhenotypeAssociation; import ubic.gemma.model.common.Describable; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.gene.GeneSet; import ubic.gemma.persistence.service.analysis.expression.diff.GeneDiffExMetaAnalysisService; import ubic.gemma.persistence.service.association.phenotype.service.PhenotypeAssociationService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentSetService; import ubic.gemma.persistence.util.MailEngine; import ubic.gemma.persistence.util.Settings; import ubic.gemma.web.remote.EntityDelegator; import java.util.*; /** * Manages data-level security (ie. can make data private). */ @Component public class SecurityControllerImpl implements SecurityController { private static final String GROUP_MANAGER_URL = Settings.getBaseUrl() + "manageGroups.html"; private static final Log log = LogFactory.getLog(SecurityControllerImpl.class); @Autowired private ExpressionExperimentService expressionExperimentService; @Autowired private ExpressionExperimentSetService expressionExperimentSetService; @Autowired private GeneDiffExMetaAnalysisService geneDiffExMetaAnalysisService; @Autowired private GeneSetService geneSetService; @Autowired private PhenotypeAssociationService phenotypeAssociationService; @Autowired private MailEngine mailEngine; @Autowired private SecurityService securityService; @Autowired private UserManager userManager; @Override public boolean addUserToGroup(String userName, String groupName) { User userTakingAction = userManager.getCurrentUser(); if (userTakingAction == null) { throw new IllegalStateException("Cannot add user to group when user is not logged in"); } User u; if (userManager.userExists(userName)) { u = userManager.findByUserName(userName); if (!u.getEnabled()) { throw new IllegalArgumentException("Sorry, that user's account is not enabled."); } securityService.addUserToGroup(userName, groupName); } else if (userManager.userWithEmailExists(userName)) { u = userManager.findByEmail(userName); if (!u.getEnabled()) { throw new IllegalArgumentException("Sorry, that user's account is not enabled."); } String uname = u.getUserName(); securityService.addUserToGroup(uname, groupName); } else { throw new IllegalArgumentException("Sorry, there is no matching user."); } /* * send the user an email. */ String emailAddress = u.getEmail(); if (StringUtils.isNotBlank(emailAddress)) { SecurityControllerImpl.log.debug("Sending email notification to " + emailAddress); SimpleMailMessage msg = new SimpleMailMessage(); msg.setTo(emailAddress); msg.setFrom(Settings.getAdminEmailAddress()); msg.setSubject("You have been added to a group on Gemma"); msg.setText(userTakingAction.getUserName() + " has added you to the group '" + groupName + "'.\nTo view groups you belong to, visit " + SecurityControllerImpl.GROUP_MANAGER_URL + "\n\nIf you believe you received this email in error, contact " + Settings.getAdminEmailAddress() + "."); mailEngine.send(msg); } return true; } @Override public String createGroup(String groupName) { if (StringUtils.isBlank(groupName) || groupName.length() < 3 || !StringUtils.isAlpha(groupName)) { throw new IllegalArgumentException( "Group name must contain only letters and must be at least 3 letters long."); } securityService.createGroup(groupName); return groupName; } @Override public void deleteGroup(String groupName) { if (!this.getGroupsUserCanEdit().contains(groupName)) { throw new IllegalArgumentException("You don't have permission to modify that group"); } /* * Additional checks for ability to remove group handled by ss. */ userManager.deleteGroup(groupName); } @Override public Integer getAuthenticatedUserCount() { return securityService.getAuthenticatedUserCount(); } @Override public Collection<String> getAuthenticatedUserNames() { return securityService.getAuthenticatedUserNames(); } @Override public Collection<UserGroupValueObject> getAvailableGroups() { Collection<String> editableGroups = this.getGroupsUserCanEdit(); Collection<String> groupsUserIsIn = this.getGroupsForCurrentUser(); Collection<String> allGroups; try { // administrator... allGroups = userManager.findAllGroups(); } catch (AccessDeniedException e) { allGroups = groupsUserIsIn; } Collection<UserGroupValueObject> result = new HashSet<>(); for (String g : allGroups) { UserGroupValueObject gvo = new UserGroupValueObject(); gvo.setCanEdit(editableGroups.contains(g)); gvo.setMember(groupsUserIsIn.contains(g)); gvo.setGroupName(g); result.add(gvo); } return result; } @Override public Collection<SidValueObject> getAvailablePrincipalSids() { List<SidValueObject> results = new ArrayList<>(); try { for (Sid s : securityService.getAvailableSids()) { SidValueObject sv = new SidValueObject(s); if (sv.isPrincipal()) { results.add(sv); } } } catch (AccessDeniedException e) { results.clear(); } Collections.sort(results); return results; } @Override public Collection<SidValueObject> getAvailableSids() { List<SidValueObject> results = new ArrayList<>(); try { for (Sid s : securityService.getAvailableSids()) { SidValueObject sv = new SidValueObject(s); results.add(sv); } } catch (AccessDeniedException e) { results.clear(); } Collections.sort(results); return results; } @Override public Collection<UserValueObject> getGroupMembers(String groupName) { Collection<UserValueObject> result = new HashSet<>(); // happens if user is not in any displayed groups. if (StringUtils.isBlank(groupName)) { return result; } List<String> usersInGroup = userManager.findUsersInGroup(groupName); for (String userName : usersInGroup) { UserDetails details = userManager.loadUserByUsername(userName); UserValueObject uvo = new UserValueObject(); uvo.setUserName(details.getUsername()); if (details instanceof UserDetailsImpl) { uvo.setEmail(((UserDetailsImpl) details).getEmail()); } uvo.setCurrentGroup(groupName); uvo.setInGroup(true); uvo.setAllowModification(true); /* * You can't remove yourself from a group, or remove users from the USER group. */ if (userName.equals(userManager.getCurrentUsername()) || groupName.equals(AuthorityConstants.USER_GROUP_NAME)) { uvo.setAllowModification(false); } result.add(uvo); } return result; } @Override // @Transactional(readOnly = true) public SecurityInfoValueObject getSecurityInfo(EntityDelegator ed) { // TODO Figure out why Transaction(readOnly = true) throws an error when this method is called from // SecurityManager.js (Bug 3941) Securable s = this.getSecurable(ed); return this.securable2VO(s); } @Override public Collection<SecurityInfoValueObject> getUsersData(String currentGroup, boolean privateOnly) { Collection<Securable> secs = new HashSet<>(); // Add experiments. secs.addAll(this.getUsersExperiments(privateOnly)); Collection<SecurityInfoValueObject> result = this.securables2VOs(secs, currentGroup); result.addAll( this.securables2VOs(geneSetService.getUsersGeneGroups(privateOnly, null, true), currentGroup)); result.addAll(this.securables2VOs(this.getUsersExperimentSets(privateOnly), currentGroup)); /* * add other types of securables here. */ return result; } @Override public boolean makeGroupReadable(EntityDelegator ed, String groupName) { Securable s = this.getSecurable(ed); securityService.makeReadableByGroup(s, groupName); return true; } @Override public boolean makeGroupWriteable(EntityDelegator ed, String groupName) { Securable s = this.getSecurable(ed); securityService.makeWriteableByGroup(s, groupName); return true; } @Override public boolean makePrivate(EntityDelegator ed) { Securable s = this.getSecurable(ed); securityService.makePrivate(s); return true; } @Override public boolean makePublic(EntityDelegator ed) { Securable s = this.getSecurable(ed); securityService.makePublic(s); return true; } @Override public boolean removeGroupReadable(EntityDelegator ed, String groupName) { Securable s = this.getSecurable(ed); securityService.makeUnreadableByGroup(s, groupName); return true; } @Override public boolean removeGroupWriteable(EntityDelegator ed, String groupName) { Securable s = this.getSecurable(ed); securityService.makeUnwriteableByGroup(s, groupName); return true; } @Override public boolean removeUsersFromGroup(String[] userNames, String groupName) { for (String userName : userNames) { securityService.removeUserFromGroup(userName, groupName); } return true; } @Override public void setExpressionExperimentService(ExpressionExperimentService expressionExperimentService) { this.expressionExperimentService = expressionExperimentService; } @Override public SecurityInfoValueObject updatePermission(SecurityInfoValueObject settings) { EntityDelegator sd = new EntityDelegator(); sd.setId(settings.getEntityId()); sd.setClassDelegatingFor(settings.getEntityClazz()); Securable s = this.getSecurable(sd); if (settings.isPubliclyReadable()) { securityService.makePublic(s); } else { securityService.makePrivate(s); } try { if (settings.getOwner().isPrincipal()) { securityService.makeOwnedByUser(s, settings.getOwner().getAuthority()); } else { // this warning is not even worth issuing if we are not an administrator. if (SecurityUtil.isUserAdmin()) SecurityControllerImpl.log.warn("Can't make groupauthority " + settings.getOwner().getAuthority() + " owner, not implemented"); } } catch (AccessDeniedException e) { SecurityControllerImpl.log.warn("Non-administrators cannot change the owner of an entity"); // okay, only works if you are administrator. } /* * This works in one of two ways. If settings.currentGroup is non-null, we just update the permissions for that * group - this may leave them unchanged. Otherwise, we update them all based on * groupsThatCanRead/groupsThatCanWrite */ String currentGroupName = settings.getCurrentGroup(); if (StringUtils.isNotBlank(currentGroupName) && !(currentGroupName.equals(AuthorityConstants.ADMIN_GROUP_NAME) || currentGroupName.equals(AuthorityConstants.AGENT_GROUP_NAME))) { // this test only makes sense for changing the group's name, not for changing the permissions // of potentially shared entities // if ( !getGroupsUserCanEdit().contains( currentGroupName ) ) { // throw new AccessDeniedException( "Access denied to permissions for group=" + currentGroupName ); // } Boolean readable = settings.isCurrentGroupCanRead(); Boolean writeable = settings.isCurrentGroupCanWrite(); if (readable) { securityService.makeReadableByGroup(s, currentGroupName); } else { securityService.makeUnreadableByGroup(s, currentGroupName); } if (writeable) { // if writable should be readable securityService.makeReadableByGroup(s, currentGroupName); securityService.makeWriteableByGroup(s, currentGroupName); } else { securityService.makeUnwriteableByGroup(s, currentGroupName); } } else { /* * Remove all group permissions - we'll set them back to what was requested. Exception: we don't allow * changes to admin or agent permissions by this route. */ for (String groupName : this.getGroupsUserCanEdit()) { if (groupName.equals(AuthorityConstants.ADMIN_GROUP_NAME) || groupName.equals(AuthorityConstants.AGENT_GROUP_NAME)) { // never changes this. continue; } securityService.makeUnreadableByGroup(s, groupName); securityService.makeUnwriteableByGroup(s, groupName); } /* * Add selected ones back */ for (String reader : settings.getGroupsThatCanRead()) { if (reader.equals(AuthorityConstants.ADMIN_GROUP_NAME) || reader.equals(AuthorityConstants.AGENT_GROUP_NAME)) { // never changes this. continue; } securityService.makeReadableByGroup(s, reader); } for (String writer : settings.getGroupsThatCanWrite()) { if (writer.equals(AuthorityConstants.ADMIN_GROUP_NAME) || writer.equals(AuthorityConstants.AGENT_GROUP_NAME)) { // never changes this. continue; } // when it is writable it should be readable securityService.makeReadableByGroup(s, writer); securityService.makeWriteableByGroup(s, writer); } } // special case for Phenocarta, changing the meta analysis, changes the permissions of all evidence linked if (s instanceof GeneDifferentialExpressionMetaAnalysis) { Collection<DifferentialExpressionEvidence> differentialExpressionEvidence = this.phenotypeAssociationService .loadEvidenceWithGeneDifferentialExpressionMetaAnalysis(s.getId(), -1); for (DifferentialExpressionEvidence d : differentialExpressionEvidence) { settings.setEntityId(d.getId()); settings.setEntityClazz(d.getClass().getName()); this.updatePermission(settings); } } SecurityControllerImpl.log.info("Updated permissions on " + s); return this.securable2VO(s); } @Override public void updatePermissions(SecurityInfoValueObject[] settings) { for (SecurityInfoValueObject so : settings) { this.updatePermission(so); } } /** * @return groups the user can edit (not just the ones they are in!) */ private Collection<String> getGroupsForCurrentUser() { return userManager.findAllGroups(); // return userManager.findGroupsForUser( userManager.getCurrentUsername() ); } private Collection<String> getGroupsForUser(String username) { if (username == null) { return new HashSet<>(); } Collection<String> results; try { results = userManager.findGroupsForUser(username); } catch (UsernameNotFoundException e) { return new HashSet<>(); } return results; } private Collection<String> getGroupsUserCanEdit() { return securityService.getGroupsUserCanEdit(userManager.getCurrentUsername()); } /** * @param ed ed * @return securable * @throws IllegalArgumentException if the Securable cannot be loaded */ private Securable getSecurable(EntityDelegator ed) { String classDelegatingFor = ed.getClassDelegatingFor(); Class<?> clazz; Securable s; try { clazz = Class.forName(classDelegatingFor); } catch (ClassNotFoundException e1) { throw new RuntimeException(e1); } if (ExpressionExperiment.class.isAssignableFrom(clazz)) { s = expressionExperimentService.load(ed.getId()); } else if (GeneSet.class.isAssignableFrom(clazz)) { s = geneSetService.load(ed.getId()); } else if (ExpressionExperimentSet.class.isAssignableFrom(clazz)) { s = expressionExperimentSetService.load(ed.getId()); } else if (PhenotypeAssociation.class.isAssignableFrom(clazz)) { s = phenotypeAssociationService.load(ed.getId()); } else if (GeneDifferentialExpressionMetaAnalysis.class.isAssignableFrom(clazz)) { s = geneDiffExMetaAnalysisService.load(ed.getId()); } else { throw new UnsupportedOperationException(clazz + " not supported by security controller yet"); } if (s == null) { throw new IllegalArgumentException("Entity does not exist or user does not have access."); } return s; } private Collection<Securable> getUsersExperiments(boolean privateOnly) { Collection<ExpressionExperiment> ees = expressionExperimentService.loadAll(); Collection<Securable> secs = new HashSet<>(); if (privateOnly) { try { secs.addAll(securityService.choosePrivate(ees)); } catch (AccessDeniedException e) { // okay, they just aren't allowed to see those. } } else { Collection<ExpressionExperiment> usersEEs = expressionExperimentService.loadAll(); secs.addAll(ees); secs.addAll(usersEEs); } return secs; } private Collection<Securable> getUsersExperimentSets(boolean privateOnly) { Collection<Securable> secs = new HashSet<>(); Collection<ExpressionExperimentSet> eeSets = expressionExperimentSetService .loadAllExperimentSetsWithTaxon(); if (privateOnly) { try { secs.addAll(securityService.choosePrivate(eeSets)); } catch (AccessDeniedException e) { // okay, they just aren't allowed to see those. } } else { secs.addAll(eeSets); } return secs; } /** * Create a fully-populated value object for the given securable. * * @param s securable * @return security info VO */ private SecurityInfoValueObject securable2VO(Securable s) { /* * Problem: this is quite slow. Can probably improve by not loading the securable at all, just load a * SecuredValueObject, but it doesn't currently have all this information. */ boolean isPublic = securityService.isPublic(s); boolean isShared = securityService.isShared(s); boolean canWrite = securityService.isEditable(s); SecurityInfoValueObject result = new SecurityInfoValueObject(s); result.setAvailableGroups(this.getGroupsForCurrentUser()); result.setPubliclyReadable(isPublic); result.setGroupsThatCanRead(securityService.getGroupsReadableBy(s)); result.setGroupsThatCanWrite(securityService.getGroupsEditableBy(s)); result.setShared(isShared); result.setOwner(new SidValueObject(securityService.getOwner(s))); result.setOwnersGroups(this.getGroupsForUser(result.getOwner().getAuthority())); result.setCurrentUserOwns(securityService.isOwnedByCurrentUser(s)); result.setCurrentUserCanwrite(canWrite); if (Describable.class.isAssignableFrom(s.getClass())) { result.setEntityDescription(((Describable) s).getDescription()); result.setEntityName(((Describable) s).getName()); } return result; } /** * @param securables securables * @param currentGroup A specific group that we're focusing on. Can be null * @return security info VOs */ private <T extends Securable> Collection<SecurityInfoValueObject> securables2VOs(Collection<T> securables, String currentGroup) { Collection<SecurityInfoValueObject> result = new HashSet<>(); if (securables.isEmpty()) { return result; } /* * Fast computations out-of-loop */ Collection<String> groupsForCurrentUser = this.getGroupsForCurrentUser(); Map<T, Boolean> privacy = securityService.arePrivate(securables); Map<T, Boolean> sharedness = securityService.areShared(securables); Map<T, Sid> owners = securityService.getOwners(securables); Map<T, Collection<String>> groupsReadableBy = securityService.getGroupsReadableBy(securables); Map<T, Collection<String>> groupsEditableBy = securityService.getGroupsEditableBy(securables); // int i = 0; // TESTING for (Securable s : securables) { Collection<String> groupsThatCanRead = groupsReadableBy.get(s); Collection<String> groupsThatCanWrite = groupsEditableBy.get(s); SecurityInfoValueObject vo = new SecurityInfoValueObject(s); vo.setCurrentGroup(currentGroup); vo.setAvailableGroups(groupsForCurrentUser); vo.setPubliclyReadable(!privacy.get(s)); vo.setShared(sharedness.get(s)); vo.setOwner(new SidValueObject(owners.get(s))); // FIXME this does not seem to be used in the UI and it fixes issue #41: https://github.com/ppavlidis/Gemma/issues/41 vo.setCurrentUserOwns(false);//securityService.isOwnedByCurrentUser( s ) ); vo.setCurrentUserCanwrite(securityService.isEditable(s)); vo.setGroupsThatCanRead(groupsThatCanRead == null ? new HashSet<String>() : groupsThatCanRead); vo.setGroupsThatCanWrite(groupsThatCanWrite == null ? new HashSet<String>() : groupsThatCanWrite); vo.setEntityClazz(s.getClass().getName()); if (currentGroup != null) { vo.setCurrentGroupCanRead(groupsThatCanRead != null && groupsThatCanRead.contains(currentGroup)); vo.setCurrentGroupCanWrite(groupsThatCanWrite != null && groupsThatCanWrite.contains(currentGroup)); } if (ExpressionExperiment.class.isAssignableFrom(s.getClass())) { vo.setEntityShortName(((ExpressionExperiment) s).getShortName()); vo.setEntityName(((ExpressionExperiment) s).getName()); } else if (Describable.class.isAssignableFrom(s.getClass())) { vo.setEntityShortName(((Describable) s).getName()); vo.setEntityName(((Describable) s).getDescription()); } result.add(vo); // if ( ++i > 10 ) break; // TESTING } return result; } }