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    @Override
096    public boolean isSupported(Object object)
097    {
098        return object instanceof ProgramItem || object instanceof OrgUnit || object instanceof CoursePart;
099    }
100    
101    /**
102     * Determines if the object is a orphan program item (without parent)
103     * @param object the object
104     * @return true if the object is a orphan program item
105     */
106    protected boolean _isOrphan(Object object)
107    {
108        if (object instanceof ProgramItem)
109        {
110            List<ProgramItem> parentProgramItems = _odfHelper.getParentProgramItems((ProgramItem) object);
111            return parentProgramItems.isEmpty();
112        }
113        else if (object instanceof OrgUnit)
114        {
115            return ((OrgUnit) object).getParentOrgUnit() == null;
116        }
117        else if (object instanceof CoursePart)
118        {
119            return ((CoursePart) object).getCourses().isEmpty();
120        }
121        
122        return false;
123    }
124    
125    /**
126     * Get the user permission on object
127     * @param user the user
128     * @param object the object
129     * @return The access result
130     */
131    protected AccessResult _getUserPermission(UserIdentity user, Object object)
132    {
133        if (_isOrphan(object) && !_hasOrgUnit(object))
134        {
135            if (user.equals(((Content) object).getCreator()))
136            {
137                return AccessResult.USER_ALLOWED;
138            }
139        }
140        return AccessResult.UNKNOWN;
141    }
142    
143    /**
144     * Determines if the object has a orgunit
145     * @param object the object
146     * @return true if the object is attach to a orgunit
147     */
148    protected boolean _hasOrgUnit(Object object)
149    {
150        if (object instanceof ProgramItem programItem)
151        {
152            return !programItem.getOrgUnits().isEmpty();
153        }
154        return false;
155    }
156    
157    /**
158     * Get the permission by users
159     * @param object the object
160     * @return the permission by users
161     */
162    protected Map<UserIdentity, AccessResult> _getPermissionByUser(Object object)
163    {
164        Map<UserIdentity, AccessResult> permissions = new HashMap<>();
165        if (_isOrphan(object) && !_hasOrgUnit(object))
166        {
167            permissions.put(((Content) object).getCreator(), AccessResult.USER_ALLOWED);
168        }
169        return permissions;
170    }
171
172    @Override
173    public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object)
174    {
175        return _getUserPermission(user, object);
176    }
177
178    @Override
179    public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
180    {
181        return _getUserPermission(user, object);
182    }
183
184    @Override
185    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
186    {
187        return Collections.EMPTY_MAP;
188    }
189
190    @Override
191    public AccessResult getPermissionForAnonymous(String rightId, Object object)
192    {
193        return AccessResult.UNKNOWN;
194    }
195
196    public AccessResult getReadAccessPermissionForAnonymous(Object object)
197    {
198        return AccessResult.UNKNOWN;
199    }
200
201    @Override
202    public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object)
203    {
204        return AccessResult.UNKNOWN;
205    }
206
207    @Override
208    public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object)
209    {
210        return AccessResult.UNKNOWN;
211    }
212
213    @Override
214    public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object)
215    {
216        return _getPermissionByUser(object);
217    }
218
219    @Override
220    public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object)
221    {
222        return _getPermissionByUser(object);
223    }
224
225    @Override
226    public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object)
227    {
228        return Collections.EMPTY_MAP;
229    }
230
231    @Override
232    public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object)
233    {
234        return Collections.EMPTY_MAP;
235    }
236
237    @Override
238    public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId)
239    {
240        return false;
241    }
242
243    @Override
244    public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups)
245    {
246        return false;
247    }
248
249    @Override
250    public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
251    {
252        return false;
253    }
254
255    @Override
256    public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
257    {
258        return false;
259    }
260
261    @Override
262    public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
263    {
264        return false;
265    }
266
267    @Override
268    public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
269    {
270        return false;
271    }
272    
273    @Override
274    public AccessExplanation getStandardAccessExplanation(AccessResult permission, Object object)
275    {
276        if (permission == AccessResult.USER_ALLOWED)
277        {
278            return new AccessExplanation(getId(), permission,
279                    new I18nizableText("plugin.odf", "PLUGINS_ODF_ORPHAN_ACCESS_CONTROLLER_EXPLANATION", _getI18nParameters(object))
280                    );
281        }
282        return AccessController.getDefaultAccessExplanation(getId(), permission);
283    }
284    
285    /**
286     * Get the context label
287     * @param object the context
288     * @return the label
289     */
290    protected Map<String, I18nizableTextParameter> _getI18nParameters(Object object)
291    {
292        Map<String, I18nizableTextParameter> params = new HashMap<>();
293        if (object instanceof Content content)
294        {
295            params.put("title", new I18nizableText(content.getTitle()));
296        }
297        
298        if (object instanceof ProgramItem item)
299        {
300            params.put("code", new I18nizableText(item.getCode()));
301        }
302        else if (object instanceof OrgUnit orgunit)
303        {
304            params.put("code", new I18nizableText(orgunit.getUAICode()));
305        }
306        else if (object instanceof CoursePart part)
307        {
308            params.put("code", new I18nizableText(part.getCode()));
309        }
310        return params;
311    }
312    
313    @Override
314    public Map<ExplanationObject, Map<Permission, AccessExplanation>> explainAllPermissions(UserIdentity identity, Set<GroupIdentity> groups)
315    {
316        Map<ExplanationObject, Map<Permission, AccessExplanation>> result = new HashMap<>();
317        
318        UserExpression userExpression = new UserExpression(DefaultContent.METADATA_CREATOR, Operator.EQ, identity);
319        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);
320        String query = ContentQueryHelper.getContentXPathQuery(new AndExpression(userExpression, contentTypeExpression));
321        AmetysObjectIterable<Content> contents = _resolver.query(query);
322        
323        for (Content content : contents)
324        {
325            Map<Permission, AccessExplanation> contextResult = new HashMap<>();
326            AccessExplanation explanation = explainPermission(identity, groups, null, content);
327            if (explanation.accessResult() != AccessResult.UNKNOWN)
328            {
329                contextResult.put(new Permission(PermissionType.ALL_RIGHTS, null), explanation);
330            }
331            
332            explanation = explainReadAccessPermission(identity, groups, content);
333            if (explanation.accessResult() != AccessResult.UNKNOWN)
334            {
335                contextResult.put(new Permission(PermissionType.READ, null), explanation);
336            }
337            
338            if (!contextResult.isEmpty())
339            {
340                ExplanationObject explanationContext = getExplanationObject(content);
341                result.put(explanationContext, contextResult);
342            }
343        }
344        
345        return result;
346    }
347
348    public I18nizableText getObjectLabel(Object object)
349    {
350        if (object instanceof Content content)
351        {
352            return ODFContentHierarchicalAccessController.getContentObjectLabel(content, _odfContentsTreeHelper);
353        }
354        throw new RightsException("Unsupported context: " + object.toString());
355    }
356
357    public I18nizableText getObjectCategory(Object object)
358    {
359        return ODFContentHierarchicalAccessController.ODF_CONTEXT_CATEGORY;
360    }
361
362}