001/*
002 *  Copyright 2016 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.odf.rights;
017
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027
028import org.ametys.cms.repository.Content;
029import org.ametys.cms.repository.ContentQueryHelper;
030import org.ametys.cms.repository.ContentTypeExpression;
031import org.ametys.cms.repository.DefaultContent;
032import org.ametys.core.group.GroupIdentity;
033import org.ametys.core.right.AccessController;
034import org.ametys.core.right.AccessController.Permission.PermissionType;
035import org.ametys.core.right.AccessExplanation;
036import org.ametys.core.right.RightProfilesDAO;
037import org.ametys.core.right.RightsException;
038import org.ametys.core.user.UserIdentity;
039import org.ametys.odf.ODFHelper;
040import org.ametys.odf.ProgramItem;
041import org.ametys.odf.course.CourseFactory;
042import org.ametys.odf.courselist.CourseListFactory;
043import org.ametys.odf.coursepart.CoursePart;
044import org.ametys.odf.coursepart.CoursePartFactory;
045import org.ametys.odf.orgunit.OrgUnit;
046import org.ametys.odf.orgunit.OrgUnitFactory;
047import org.ametys.odf.program.ContainerFactory;
048import org.ametys.odf.program.ProgramFactory;
049import org.ametys.odf.program.SubProgramFactory;
050import org.ametys.odf.tree.ODFContentsTreeHelper;
051import org.ametys.plugins.repository.AmetysObjectIterable;
052import org.ametys.plugins.repository.AmetysObjectResolver;
053import org.ametys.plugins.repository.query.expression.AndExpression;
054import org.ametys.plugins.repository.query.expression.Expression;
055import org.ametys.plugins.repository.query.expression.Expression.Operator;
056import org.ametys.plugins.repository.query.expression.UserExpression;
057import org.ametys.runtime.i18n.I18nizableText;
058import org.ametys.runtime.i18n.I18nizableTextParameter;
059import org.ametys.runtime.plugin.component.AbstractLogEnabled;
060import org.ametys.runtime.plugin.component.PluginAware;
061
062/**
063 * This access controller give access the content's creator, regardless of the required right,
064 * if and only if the ODF content is still orphan (during creation process for example)
065 */
066public class ODFOrphanContentAccessController extends AbstractLogEnabled implements AccessController, Serviceable, PluginAware
067{
068    /** The right profile DAO */
069    protected RightProfilesDAO _profileDAO;
070    /** The ametys object resolver */
071    protected AmetysObjectResolver _resolver;
072    /** The ODF contents tree helper */
073    protected ODFContentsTreeHelper _odfContentsTreeHelper;
074    private ODFHelper _odfHelper;
075    private String _id;
076
077    public void service(ServiceManager manager) throws ServiceException
078    {
079        _odfContentsTreeHelper = (ODFContentsTreeHelper) manager.lookup(ODFContentsTreeHelper.ROLE);
080        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
081        _profileDAO = (RightProfilesDAO) manager.lookup(RightProfilesDAO.ROLE);
082        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
083    }
084    
085    public void setPluginInfo(String pluginName, String featureName, String id)
086    {
087        _id = id;
088    }
089    
090    public String getId()
091    {
092        return _id;
093    }
094    
095    public boolean supports(Object object)
096    {
097        return object instanceof ProgramItem || object instanceof OrgUnit || object instanceof CoursePart;
098    }
099    
100    /**
101     * Determines if the object is a orphan program item (without parent)
102     * @param object the object
103     * @return true if the object is a orphan program item
104     */
105    protected boolean _isOrphan(Object object)
106    {
107        if (object instanceof ProgramItem)
108        {
109            List<ProgramItem> parentProgramItems = _odfHelper.getParentProgramItems((ProgramItem) object);
110            return parentProgramItems.isEmpty();
111        }
112        else if (object instanceof OrgUnit)
113        {
114            return ((OrgUnit) object).getParentOrgUnit() == null;
115        }
116        else if (object instanceof CoursePart)
117        {
118            return ((CoursePart) object).getCourses().isEmpty();
119        }
120        
121        return false;
122    }
123    
124    /**
125     * Get the user permission on object
126     * @param user the user
127     * @param object the object
128     * @return The access result
129     */
130    protected AccessResult _getUserPermission(UserIdentity user, Object object)
131    {
132        if (_isOrphan(object) && !_hasOrgUnit(object))
133        {
134            if (user.equals(((Content) object).getCreator()))
135            {
136                return AccessResult.USER_ALLOWED;
137            }
138        }
139        return AccessResult.UNKNOWN;
140    }
141    
142    /**
143     * Determines if the object has a orgunit
144     * @param object the object
145     * @return true if the object is attach to a orgunit
146     */
147    protected boolean _hasOrgUnit(Object object)
148    {
149        if (object instanceof ProgramItem programItem)
150        {
151            return !programItem.getOrgUnits().isEmpty();
152        }
153        return false;
154    }
155    
156    /**
157     * Get the permission by users
158     * @param object the object
159     * @return the permission by users
160     */
161    protected Map<UserIdentity, AccessResult> _getPermissionByUser(Object object)
162    {
163        Map<UserIdentity, AccessResult> permissions = new HashMap<>();
164        if (_isOrphan(object) && !_hasOrgUnit(object))
165        {
166            permissions.put(((Content) object).getCreator(), AccessResult.USER_ALLOWED);
167        }
168        return permissions;
169    }
170
171    @Override
172    public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object)
173    {
174        return _getUserPermission(user, object);
175    }
176
177    @Override
178    public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
179    {
180        return _getUserPermission(user, object);
181    }
182
183    @Override
184    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
185    {
186        return Collections.EMPTY_MAP;
187    }
188
189    @Override
190    public AccessResult getPermissionForAnonymous(String rightId, Object object)
191    {
192        return AccessResult.UNKNOWN;
193    }
194
195    public AccessResult getReadAccessPermissionForAnonymous(Object object)
196    {
197        return AccessResult.UNKNOWN;
198    }
199
200    @Override
201    public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object)
202    {
203        return AccessResult.UNKNOWN;
204    }
205
206    @Override
207    public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object)
208    {
209        return AccessResult.UNKNOWN;
210    }
211
212    @Override
213    public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object)
214    {
215        return _getPermissionByUser(object);
216    }
217
218    @Override
219    public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object)
220    {
221        return _getPermissionByUser(object);
222    }
223
224    @Override
225    public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object)
226    {
227        return Collections.EMPTY_MAP;
228    }
229
230    @Override
231    public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object)
232    {
233        return Collections.EMPTY_MAP;
234    }
235
236    @Override
237    public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId)
238    {
239        return false;
240    }
241
242    @Override
243    public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups)
244    {
245        return false;
246    }
247
248    @Override
249    public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
250    {
251        return false;
252    }
253
254    @Override
255    public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
256    {
257        return false;
258    }
259
260    @Override
261    public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
262    {
263        return false;
264    }
265
266    @Override
267    public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
268    {
269        return false;
270    }
271    
272    public AccessExplanation explainReadAccessPermission(UserIdentity user, Set<GroupIdentity> groups, Object object)
273    {
274        return _getAccessExplanation(getReadAccessPermission(user, groups, object), object);
275    }
276    
277    public AccessExplanation explainPermission(UserIdentity user, Set<GroupIdentity> groups, String rightId, Object object)
278    {
279        return _getAccessExplanation(getPermission(user, groups, rightId, object), object);
280    }
281    
282    private AccessExplanation _getAccessExplanation(AccessResult permission, Object object)
283    {
284        if (permission == AccessResult.USER_ALLOWED)
285        {
286            return new AccessExplanation(getId(), permission,
287                    new I18nizableText("plugin.odf", "PLUGINS_ODF_ORPHAN_ACCESS_CONTROLLER_EXPLANATION", _getI18nParameters(object))
288                    );
289        }
290        return AccessController.getDefaultAccessExplanation(getId(), permission);
291    }
292    
293    /**
294     * Get the context label
295     * @param object the context
296     * @return the label
297     */
298    protected Map<String, I18nizableTextParameter> _getI18nParameters(Object object)
299    {
300        Map<String, I18nizableTextParameter> params = new HashMap<>();
301        if (object instanceof Content content)
302        {
303            params.put("title", new I18nizableText(content.getTitle()));
304        }
305        
306        if (object instanceof ProgramItem item)
307        {
308            params.put("code", new I18nizableText(item.getDisplayCode()));
309        }
310        else if (object instanceof OrgUnit orgunit)
311        {
312            params.put("code", new I18nizableText(orgunit.getDisplayCode()));
313        }
314        else if (object instanceof CoursePart part)
315        {
316            params.put("code", new I18nizableText(part.getDisplayCode()));
317        }
318        return params;
319    }
320    
321    @Override
322    public Map<ExplanationObject, Map<Permission, AccessExplanation>> explainAllPermissions(UserIdentity identity, Set<GroupIdentity> groups, Set<Object> workspacesContexts)
323    {
324        Map<ExplanationObject, Map<Permission, AccessExplanation>> result = new HashMap<>();
325        
326        if (workspacesContexts.contains("/cms"))
327        {
328            
329            UserExpression userExpression = new UserExpression(DefaultContent.METADATA_CREATOR, Operator.EQ, identity);
330            Expression contentTypeExpression = new ContentTypeExpression(Operator.EQ, ProgramFactory.PROGRAM_CONTENT_TYPE, SubProgramFactory.SUBPROGRAM_CONTENT_TYPE, CourseFactory.COURSE_CONTENT_TYPE, ContainerFactory.CONTAINER_CONTENT_TYPE, CoursePartFactory.COURSE_PART_CONTENT_TYPE, CourseListFactory.COURSE_LIST_CONTENT_TYPE, OrgUnitFactory.ORGUNIT_CONTENT_TYPE);
331            String query = ContentQueryHelper.getContentXPathQuery(new AndExpression(userExpression, contentTypeExpression));
332            AmetysObjectIterable<Content> contents = _resolver.query(query);
333            
334            for (Content content : contents)
335            {
336                Map<Permission, AccessExplanation> contextResult = new HashMap<>();
337                AccessExplanation explanation = explainPermission(identity, groups, null, content);
338                if (explanation.accessResult() != AccessResult.UNKNOWN)
339                {
340                    contextResult.put(new Permission(PermissionType.ALL_RIGHTS, null), explanation);
341                }
342                
343                explanation = explainReadAccessPermission(identity, groups, content);
344                if (explanation.accessResult() != AccessResult.UNKNOWN)
345                {
346                    contextResult.put(new Permission(PermissionType.READ, null), explanation);
347                }
348                
349                if (!contextResult.isEmpty())
350                {
351                    ExplanationObject explanationContext = getExplanationObject(content);
352                    result.put(explanationContext, contextResult);
353                }
354            }
355        }
356        
357        return result;
358    }
359    
360    public Map<Permission, AccessExplanation> explainAllPermissionsForAnonymous(Object object)
361    {
362        return Map.of();
363    }
364    
365    public Map<Permission, AccessExplanation> explainAllPermissionsForAnyConnected(Object object)
366    {
367        return Map.of();
368    }
369    
370    public Map<UserIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByUser(Object object)
371    {
372        UserIdentity creator = ((Content) object).getCreator();
373        
374        AccessResult access = _getUserPermission(creator, object);
375        if (access == AccessResult.UNKNOWN)
376        {
377            return Map.of();
378        }
379        
380        AccessExplanation explanation = _getAccessExplanation(access, object);
381        return Map.of(
382            creator,
383            Map.of(
384                new Permission(PermissionType.READ, null), explanation,
385                new Permission(PermissionType.ALL_RIGHTS, null), explanation
386            )
387        );
388    }
389    
390    public Map<GroupIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByGroup(Object object)
391    {
392        return Map.of();
393    }
394    
395    public I18nizableText getObjectLabel(Object object)
396    {
397        if (object instanceof Content content)
398        {
399            return ODFContentHierarchicalAccessController.getContentObjectLabel(content, _odfContentsTreeHelper);
400        }
401        throw new RightsException("Unsupported context: " + object.toString());
402    }
403
404    public I18nizableText getObjectCategory(Object object)
405    {
406        return ODFContentHierarchicalAccessController.ODF_CONTEXT_CATEGORY;
407    }
408}