/**
* Koya is an alfresco module that provides a corporate orientated dataroom.
*
* Copyright (C) Itl Developpement 2014
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see `<http://www.gnu.org/licenses/>`.
*/
package fr.itldev.koya.alfservice.security;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.alfresco.repo.invitation.InvitationSearchCriteriaImpl;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.site.SiteDoesNotExistException;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.invitation.Invitation;
import org.alfresco.service.cmr.invitation.InvitationSearchCriteria;
import org.alfresco.service.cmr.invitation.InvitationService;
import org.alfresco.service.cmr.invitation.NominatedInvitation;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.LimitBy;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.util.Pair;
import org.alfresco.util.collections.CollectionUtils;
import org.alfresco.util.collections.Filter;
import org.alfresco.util.collections.Function;
import org.apache.log4j.Logger;
import fr.itldev.koya.action.CleanUserPermissionsActionExecuter;
import fr.itldev.koya.alfservice.KoyaMailService;
import fr.itldev.koya.alfservice.KoyaNodeService;
import fr.itldev.koya.alfservice.UserService;
import fr.itldev.koya.exception.KoyaServiceException;
import fr.itldev.koya.model.KoyaNode;
import fr.itldev.koya.model.exceptions.KoyaErrorCodes;
import fr.itldev.koya.model.impl.Company;
import fr.itldev.koya.model.impl.User;
import fr.itldev.koya.model.impl.UserRole;
import fr.itldev.koya.model.permissions.SitePermission;
public class CompanyAclService {
private Logger logger = Logger.getLogger(this.getClass());
protected SiteService siteService;
protected UserService userService;
protected InvitationService invitationService;
protected AuthorityService authorityService;
protected AuthenticationService authenticationService;
protected ActionService actionService;
protected SearchService searchService;
protected PersonService personService;
protected KoyaNodeService koyaNodeService;
protected KoyaMailService koyaMailService;
/*
* Invitation Url Params
*/
private String koyaClientServerPath;
private String koyaClientAcceptUrl;
private String koyaClientRejectUrl;
// <editor-fold defaultstate="collapsed" desc="getters/setters">
public void setAuthorityService(AuthorityService authorityService) {
this.authorityService = authorityService;
}
public void setAuthenticationService(
AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
public void setActionService(ActionService actionService) {
this.actionService = actionService;
}
public void setSiteService(SiteService siteService) {
this.siteService = siteService;
}
public void setKoyaMailService(KoyaMailService koyaMailService) {
this.koyaMailService = koyaMailService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public void setInvitationService(InvitationService invitationService) {
this.invitationService = invitationService;
}
public void setKoyaNodeService(KoyaNodeService koyaNodeService) {
this.koyaNodeService = koyaNodeService;
}
//
public void setKoyaClientServerPath(String koyaClientServerPath) {
this.koyaClientServerPath = koyaClientServerPath;
}
public String getKoyaClientServerPath() {
return this.koyaClientServerPath;
}
public void setKoyaClientAcceptUrl(String koyaClientAcceptUrl) {
this.koyaClientAcceptUrl = koyaClientAcceptUrl;
}
public String getKoyaClientAcceptUrl() {
return this.koyaClientAcceptUrl;
}
public void setKoyaClientRejectUrl(String koyaClientRejectUrl) {
this.koyaClientRejectUrl = koyaClientRejectUrl;
}
public String getKoyaClientRejectUrl() {
return this.koyaClientRejectUrl;
}
public SearchService getSearchService() {
return searchService;
}
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
public PersonService getPersonService() {
return personService;
}
public void setPersonService(PersonService personService) {
this.personService = personService;
}
// </editor-fold>
// TODO refine by userTypes : Collaborators Roles, Client Roles
public List<UserRole> getAvailableRoles(Company c)
throws KoyaServiceException {
try {
List<UserRole> userRoles = new ArrayList<>();
for (String r : SitePermission.getAllAsString()) {
userRoles.add(new UserRole(r));
}
return userRoles;
} catch (SiteDoesNotExistException ex) {
throw new KoyaServiceException(
KoyaErrorCodes.COMPANY_SITE_NOT_FOUND);
}
}
/**
* List both validated users and pending invitation users.
*
* @param companyName
* @param permissionsFilter
* @return
*/
public List<User> listMembers(String companyName,
List<SitePermission> permissionsFilter) {
List<User> members = new ArrayList<>();
members.addAll(listMembersValidated(companyName, permissionsFilter));
members.addAll(listMembersPendingInvitation(companyName,
permissionsFilter));
return members;
}
/**
*
* @param parent
* @param skipCount
* @param maxItems
* @param onlyFolders
* @return
* @throws KoyaServiceException
*/
public Pair<List<User>, Pair<Long, Long>> listMembersPaginated(
String companyName, List<String> roles, final Integer skipCount,
final Integer maxItems, final boolean withAdmins,
final String sortField, final Boolean ascending)
throws KoyaServiceException {
Integer skip = skipCount;
Integer max = maxItems;
if (skipCount == null) {
skip = Integer.valueOf(0);
}
if (maxItems == null) {
max = Integer.MAX_VALUE;
}
SearchParameters sp = new SearchParameters();
sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
sp.setLanguage(SearchService.LANGUAGE_LUCENE);
sp.setLimitBy(LimitBy.FINAL_SIZE);
sp.setSkipCount(skip);
sp.setMaxItems(max);
if (sortField != null) {
sp.addSort(sortField, ascending);
}
StringBuffer query = new StringBuffer();
if (!roles.isEmpty()) {
String or = "";
for (String role : roles) {
query.append(or);
query.append("PATH:\"/sys:system/sys:authorities/cm:")
.append(siteService.getSiteRoleGroup(companyName, role))
.append("/*\"");
or = "OR ";
}
} else {
query.append("PATH:\"/sys:system/sys:authorities/cm:")
.append(siteService.getSiteGroup(companyName))
.append("/*/*\"");
}
if (!withAdmins) {
query.insert(0, "(").append(") AND");
query.append(" NOT PARENT:\"workspace://SpacesStore/GROUP_ALFRESCO_ADMINISTRATORS\"");
}
logger.debug(query.toString());
sp.setQuery(query.toString());
ResultSet results = searchService.query(sp);
// results.
/**
* Transform List<FileInfo> as List<KoyaNode>
*/
List<User> users = CollectionUtils.transform(results.getNodeRefs(),
new Function<NodeRef, User>() {
@Override
public User apply(NodeRef nodeRef) {
return userService.getUserByUsername(personService
.getPerson(nodeRef).getUserName());
}
});
return new Pair<List<User>, Pair<Long, Long>>(users,
new Pair<Long, Long>(results.getNumberFound(),
results.getNumberFound()));
}
/**
* List members of the company already validated.
*
* Automaticly excludes Alfresco administrators.
*
* @param companyName
* @param permissionsFilter
* @return
*/
public List<User> listMembersValidated(String companyName,
List<SitePermission> permissionsFilter) {
final List<String> permissions = CollectionUtils
.toListOfStrings(permissionsFilter);
Map<String, String> members = siteService.listMembers(companyName,
null, null, 0);
List<Map.Entry<String, String>> companyMembers = new ArrayList(
members.entrySet());
if (permissionsFilter != null && !permissionsFilter.isEmpty()) {
companyMembers = CollectionUtils.filter(companyMembers,
new Filter<Map.Entry<String, String>>() {
@Override
public Boolean apply(Map.Entry<String, String> member) {
return permissions.contains(member.getValue())
&& !authorityService
.isAdminAuthority(member.getKey());
}
});
}
List<User> usersOfCompanyValidated = CollectionUtils.transform(
companyMembers,
new Function<Map.Entry<String, String>, User>() {
@Override
public User apply(Map.Entry<String, String> entry) {
return userService.getUserByUsername(entry.getKey());
}
});
return usersOfCompanyValidated;
}
/**
* List Members of the company with pending invitation.
*
* @param companyName
* @param permissionsFilter
* @return
*/
public List<User> listMembersPendingInvitation(final String companyName,
final List<SitePermission> permissionsFilter) {
List<Invitation> pendinginvitations = getPendingInvite(companyName,
null, null);
final List<String> permissions = CollectionUtils
.toListOfStrings(permissionsFilter);
if (permissionsFilter != null && !permissionsFilter.isEmpty()) {
pendinginvitations = CollectionUtils.filter(pendinginvitations,
new Filter<Invitation>() {
@Override
public Boolean apply(Invitation i) {
return permissions.contains(i.getRoleName());
}
});
}
List<User> usersOfCompanyPendingInvitation = CollectionUtils.transform(
pendinginvitations, new Function<Invitation, User>() {
@Override
public User apply(Invitation invitation) {
return userService.getUserByUsername(invitation
.getInviteeUserName());
}
});
return usersOfCompanyPendingInvitation;
}
public List<Invitation> getPendingInvite(String companyId,
String inviterId, String inviteeId) {
final InvitationSearchCriteriaImpl criteria = new InvitationSearchCriteriaImpl();
criteria.setInvitationType(InvitationSearchCriteria.InvitationType.NOMINATED);
criteria.setResourceType(Invitation.ResourceType.WEB_SITE);
if (inviterId != null) {
criteria.setInviter(inviterId);
}
if (inviteeId != null) {
criteria.setInvitee(inviteeId);
}
if (companyId != null) {
criteria.setResourceName(companyId);
}
return AuthenticationUtil
.runAsSystem(new AuthenticationUtil.RunAsWork<List<Invitation>>() {
@Override
public List<Invitation> doWork() throws Exception {
return invitationService.searchInvitation(criteria);
}
});
}
/**
*
* @param username
* @return List of companies a user belong to
*/
public List<Company> listCompany(String username) {
List<SiteInfo> l = siteService.listSites(username);
List<Company> res = CollectionUtils.transform(l,
new Function<SiteInfo, Company>() {
@Override
public Company apply(SiteInfo siteInfo) {
try {
return koyaNodeService.getKoyaNode(
siteInfo.getNodeRef(), Company.class);
} catch (KoyaServiceException ex) {
}
return null;
}
});
return res;
}
/**
* Checks if current logged user is company manager on specified company.
*
* If company throws SiteDoesNotExistException, it may not have already been
* indexed. return current user is admin in this case
*
* @param companyName
* @return
* @throws fr.itldev.koya.exception.KoyaServiceException
*/
public Boolean isCompanyManager(String companyName) throws KoyaServiceException {
try {
return SitePermission.MANAGER.equals(siteService.getMembersRole(companyName,
authenticationService.getCurrentUserName()));
} catch (SiteDoesNotExistException ex) {
logger.warn("isCompanyManager : company " + companyName
+ "doesn't exists or not indexed yet : return isadminauthority");
return authorityService.isAdminAuthority(authenticationService.getCurrentUserName());
}
}
/**
* Returns SitePermission of user on Company if exists.
*
* @param c
* @param u
* @return
*/
public SitePermission getSitePermission(Company c, User u) {
// try with validated members
try {
String roleOfSite = siteService.getMembersRole(c.getName(),
u.getUserName());
if (roleOfSite != null) {
return SitePermission.valueOf(roleOfSite);
}
} catch (NullPointerException npe) {
logger.error("npe occurs " + npe.toString());
}
// if not found, try with pending invitation users
Iterator<Invitation> it = getPendingInvite(c.getName(), null,
u.getUserName()).iterator();
if (it.hasNext()) {
return SitePermission.valueOf(it.next().getRoleName());
}
// if no results return null;
return null;
}
/**
* Invite user to company with defined roleName. sharedItemIs optionnal
*
* Returns invitation if processed
*
*
* @param c
* @param userMail
* @param permission
* @return
* @throws KoyaServiceException
*/
public NominatedInvitation inviteMember(final Company c,
final String userMail, final SitePermission permission,
final KoyaNode sharedItem) throws KoyaServiceException {
// TODO unique email unicity validation (if call inviteMember,
// user should never already exist)
User uTmp = null;
try {
uTmp = userService.getUserByEmail(userMail);
} catch (KoyaServiceException kse) {
// Silently catch exception
}
final User u = uTmp;
if (u == null || getSitePermission(c, u) == null) {
/**
* Workaround to resolve invite by user bug :
*
*
*
* https://forums.alfresco.com/forum/installation-upgrades-
* configuration-integration/configuration/site-invite-failures
* https://issues.alfresco.com/jira/browse/ALF-20897
* http://forums.alfresco
* .com/forum/installation-upgrades-configuration
* -integration/configuration/problem-invite-external-users
*
*
*/
NominatedInvitation invitation = AuthenticationUtil
.runAsSystem(new AuthenticationUtil.RunAsWork<NominatedInvitation>() {
@Override
public NominatedInvitation doWork() throws Exception {
NominatedInvitation invitation = invitationService
.inviteNominated(null, userMail, userMail,
Invitation.ResourceType.WEB_SITE,
c.getName(), permission.toString(),
koyaClientServerPath,
koyaClientAcceptUrl,
koyaClientRejectUrl);
koyaMailService.sendInviteMail(invitation
.getInviteId());
return invitation;
}
});
return invitation;
} else {
throw new KoyaServiceException(
KoyaErrorCodes.INVITATION_USER_ALREADY_INVITED,
"User already invited for this company");
}
}
/**
* Set user identified by userName specified userRole in companyName
* context.
*
* User MUST already be a member of the company
*
* @param c
* @param u
* @param role
* @throws KoyaServiceException
*/
public void setRole(Company c, User u, SitePermission role)
throws KoyaServiceException {
// checks if user is already a company member
SitePermission roleSite = getSitePermission(c, u);
if (roleSite == null) {
throw new KoyaServiceException(
KoyaErrorCodes.SECU_USER_MUSTBE_COMPANY_MEMBER_TO_CHANGE_COMPANYROLE);
}
final InvitationSearchCriteriaImpl criteria = new InvitationSearchCriteriaImpl();
criteria.setInvitationType(InvitationSearchCriteria.InvitationType.NOMINATED);
criteria.setResourceType(Invitation.ResourceType.WEB_SITE);
criteria.setResourceName(c.getName());
criteria.setInvitee(u.getUserName());
List<Invitation> invitations = AuthenticationUtil
.runAsSystem(new AuthenticationUtil.RunAsWork<List<Invitation>>() {
@Override
public List<Invitation> doWork() throws Exception {
return invitationService.searchInvitation(criteria);
}
});
if (!invitations.isEmpty()) {
throw new KoyaServiceException(
KoyaErrorCodes.SECU_CANT_MODIFY_USER_PENDING_INVITE_ROLE);
} else {
try {
siteService.setMembership(c.getName(), u.getUserName(),
role.toString());
} catch (SiteDoesNotExistException ex) {
throw new KoyaServiceException(
KoyaErrorCodes.COMPANY_SITE_NOT_FOUND);
}
}
}
/**
* Revoke user access to defined company.
*
* @param c
* @param u
* @throws KoyaServiceException
*/
public void removeFromMembers(final Company c, final User u)
throws KoyaServiceException {
List<Invitation> invitations = getPendingInvite(c.getName(), null,
u.getUserName());
if (invitations.isEmpty()) {
siteService.removeMembership(c.getName(), u.getUserName());
// run backend action that cleans all users koya specific
// permissions
// on company spaces he can access
try {
Map<String, Serializable> paramsClean = new HashMap<>();
paramsClean.put("userName", u.getUserName());
Action cleanUserAuth = actionService.createAction(
CleanUserPermissionsActionExecuter.NAME, paramsClean);
cleanUserAuth.setExecuteAsynchronously(true);
actionService.executeAction(cleanUserAuth,
siteService.getSite(c.getName()).getNodeRef());
} catch (InvalidNodeRefException ex) {
logger.error("Error cleaning user " + u.getUserName()
+ " permissions on spaces while revoking "
+ c.getName() + "access - " + ex.toString());
}
} else {
for (final Invitation i : invitations) {
AuthenticationUtil
.runAsSystem(new AuthenticationUtil.RunAsWork<Void>() {
@Override
public Void doWork() throws Exception {
try {
invitationService.cancel(i.getInviteId());
} catch (Exception e) {
logger.error("Error removing user "
+ u.getUserName()
+ " invitation while revoking "
+ c.getName() + "access - "
+ e.toString());
}
return null;
}
});
}
}
}
}