001/*
002 *  Copyright 2017 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.cms.rights.solrchecking;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Objects;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.activity.Initializable;
029import org.apache.avalon.framework.context.Context;
030import org.apache.avalon.framework.context.ContextException;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.parameters.Parameters;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.cocoon.acting.ServiceableAction;
036import org.apache.cocoon.components.ContextHelper;
037import org.apache.cocoon.environment.ObjectModelHelper;
038import org.apache.cocoon.environment.Redirector;
039import org.apache.cocoon.environment.Request;
040import org.apache.cocoon.environment.SourceResolver;
041import org.apache.commons.lang3.StringUtils;
042import org.slf4j.Logger;
043
044import org.ametys.core.cocoon.JSonReader;
045import org.ametys.core.group.GroupIdentity;
046import org.ametys.core.group.GroupManager;
047import org.ametys.core.right.AllowedUsers;
048import org.ametys.core.right.RightManager;
049import org.ametys.core.user.UserIdentity;
050import org.ametys.core.util.AvalonLoggerAdapter;
051import org.ametys.plugins.repository.AmetysObject;
052import org.ametys.plugins.repository.AmetysObjectResolver;
053import org.ametys.plugins.repository.AmetysRepositoryException;
054import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
055import org.ametys.runtime.config.Config;
056
057/**
058 * Action called to know allowed users for each {@link AmetysObject} in a list.
059 */
060public class AllowedUsersByObjectAction extends ServiceableAction implements Contextualizable, Initializable
061{
062    /** The Ametys object resolver */
063    protected AmetysObjectResolver _resolver;
064    /** The right manager */
065    protected RightManager _rightManager;
066    /** The group manager */
067    protected GroupManager _groupManager;
068    /** The Extension Point for doing additonal operations before and after calling {@link RightManager#getReadAccessAllowedUsers(Object)} in the iteration over objectIds in {@link #_act(String, Map, List)} */
069    protected AllowedUsersActionAdditionalOperationsExtensionPoint _allowedUsersActionAdditionalOperationsEP;
070    
071    private Context _context;
072    private Logger _logger;
073    
074    @Override
075    public void service(ServiceManager smanager) throws ServiceException
076    {
077        super.service(smanager);
078        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
079        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
080        _groupManager = (GroupManager) smanager.lookup(GroupManager.ROLE);
081        _allowedUsersActionAdditionalOperationsEP = (AllowedUsersActionAdditionalOperationsExtensionPoint) smanager.lookup(AllowedUsersActionAdditionalOperationsExtensionPoint.ROLE);
082    }
083    
084    @Override
085    public void contextualize(Context context) throws ContextException
086    {
087        _context = context;
088    }
089    
090    @Override
091    public void initialize() throws Exception
092    {
093        // will be called after #enableLogging, so getLogger() will not return null
094        _logger = new AvalonLoggerAdapter(getLogger());
095    }
096    
097    @Override
098    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
099    {
100        Request request = ObjectModelHelper.getRequest(objectModel);
101        
102        Map<String, Object> result = new HashMap<>();
103        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
104        
105        String coreName = parameters.getParameter("coreName");
106        String objectIdsAsString = request.getParameter("ids");
107        
108        Map<String, Map<String, Object>> responseMap = new LinkedHashMap<>();
109        result.put("response", responseMap);
110        List<String> unresolvedIds = new ArrayList<>();
111        result.put("unresolvedIds", unresolvedIds);
112
113
114        // Retrieve the current workspace.
115        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
116        
117        try
118        {
119            // Force the workspace.
120            String solrCorePrefix = Config.getInstance().getValueAsString("cms.solr.core.prefix");
121            if (coreName.startsWith(solrCorePrefix))
122            {
123                String workspaceName = coreName.substring(solrCorePrefix.length());
124                RequestAttributeWorkspaceSelector.setForcedWorkspace(request, workspaceName);
125                _act(objectIdsAsString, responseMap, unresolvedIds);
126            }
127            else
128            {
129                throw new IllegalArgumentException("The core name '" + coreName + "' does not starts with the prefix '" + solrCorePrefix + "'");
130            }
131        }
132        finally
133        {
134            // Restore context
135            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
136        }
137        
138        return EMPTY_MAP;
139    }
140    
141    private void _act(String objectIdsAsString, Map<String, Map<String, Object>> responseMap, List<String> unresolvedIds)
142    {
143        Request request = ContextHelper.getRequest(_context);
144        
145        if (objectIdsAsString == null)
146        {
147            throw new IllegalArgumentException("{} was called with no parameter 'ids'. It is very likely that this request has exceeded the Tomcat maximum POST size. ");
148        }
149        String[] objectIds = StringUtils.split(objectIdsAsString, ',');
150        _logger.info("Start retrieving allowed users for {} ametys object id(s)", objectIds.length);
151        for (String objectId : objectIds)
152        {
153            AmetysObject ametysObject;
154            try
155            {
156                ametysObject = _resolver.resolveById(objectId);
157            }
158            catch (AmetysRepositoryException e)
159            {
160                unresolvedIds.add(objectId);
161                _logger.info("The object with id '{}' cannot be retrieved.", objectId, e);
162                continue;
163            }
164            
165            try
166            {
167                _allowedUsersActionAdditionalOperationsEP.beforeGettingAllowedUsers(ametysObject, request);
168                AllowedUsers allowedUsersObj = _rightManager.getReadAccessAllowedUsers(ametysObject);
169                _allowedUsersActionAdditionalOperationsEP.afterGettingAllowedUsers(ametysObject, request);
170                
171                Boolean anonymous = allowedUsersObj.isAnonymousAllowed();
172                Boolean anyConnected = allowedUsersObj.isAnyConnectedUserAllowed();
173                List<String> allowedGroupUsers = _usersFromGroupsAsStrings(allowedUsersObj.getAllowedGroups());
174                List<String> deniedGroupUsers = _usersFromGroupsAsStrings(allowedUsersObj.getDeniedGroups());
175                List<String> allowedUsers = _usersAsStrings(allowedUsersObj.getAllowedUsers());
176                List<String> deniedUsers = _usersAsStrings(allowedUsersObj.getDeniedUsers());
177                
178                Map<String, Object> unresolvedAllowedUsers = new LinkedHashMap<>();
179                unresolvedAllowedUsers.put("anonymous", anonymous);
180                unresolvedAllowedUsers.put("anyConnected", anyConnected);
181                unresolvedAllowedUsers.put("allowedGroupUsers", allowedGroupUsers);
182                unresolvedAllowedUsers.put("deniedGroupUsers", deniedGroupUsers);
183                unresolvedAllowedUsers.put("allowedUsers", allowedUsers);
184                unresolvedAllowedUsers.put("deniedUsers", deniedUsers);
185                responseMap.put(objectId, unresolvedAllowedUsers);
186            }
187            catch (Exception e)
188            {
189                unresolvedIds.add(objectId);
190                _logger.error("An unexpected exception occured when trying to get read access of object with id '{}'.", objectId, e);
191            }
192        }
193        
194        _logger.info("End retrieving allowed users for {} ametys objects ids. {} id(s) could not be resolved.", objectIds.length, unresolvedIds.size());
195    }
196    
197    private List<String> _usersFromGroupsAsStrings(Collection<GroupIdentity> groups)
198    {
199        if (groups == null)
200        {
201            return Collections.EMPTY_LIST;
202        }
203        
204        return groups.stream()
205                     .map(_groupManager::getGroup)
206                     .filter(Objects::nonNull)
207                     .flatMap(group -> group.getUsers().stream())
208                     .distinct()
209                     .map(UserIdentity::userIdentityToString)
210                     .collect(Collectors.toList());
211    }
212    
213    private List<String> _usersAsStrings(Collection<UserIdentity> users)
214    {
215        if (users == null)
216        {
217            return Collections.EMPTY_LIST;
218        }
219        
220        return users.stream()
221                    .map(UserIdentity::userIdentityToString)
222                    .collect(Collectors.toList());
223    }
224}