001/*
002 *  Copyright 2020 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.plugins.ugc.accesscontroller;
017
018import java.util.Collection;
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;
027import org.apache.cocoon.components.ContextHelper;
028import org.apache.commons.collections.MapUtils;
029import org.apache.commons.lang3.StringUtils;
030
031import org.ametys.cms.contenttype.ContentTypesHelper;
032import org.ametys.cms.repository.Content;
033import org.ametys.cms.repository.ContentQueryHelper;
034import org.ametys.cms.repository.ContentTypeOrMixinExpression;
035import org.ametys.cms.repository.DefaultContent;
036import org.ametys.cms.rights.ContentAccessController;
037import org.ametys.core.group.GroupIdentity;
038import org.ametys.core.right.AccessController;
039import org.ametys.core.right.AccessExplanation;
040import org.ametys.core.right.RightsException;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.plugins.core.impl.right.AbstractRightBasedAccessController;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.query.expression.AndExpression;
045import org.ametys.plugins.repository.query.expression.Expression;
046import org.ametys.plugins.repository.query.expression.Expression.Operator;
047import org.ametys.plugins.repository.query.expression.StringExpression;
048import org.ametys.plugins.repository.query.expression.UserExpression;
049import org.ametys.plugins.ugc.UGCConstants;
050import org.ametys.runtime.i18n.I18nizableText;
051import org.ametys.web.WebHelper;
052import org.ametys.web.repository.SiteAwareAmetysObject;
053
054/**
055 * {@link AccessController} so creator of a UGC content types receive edit/delete rights on it
056 *
057 */
058public class UGCCreatorContentAccessController extends AbstractRightBasedAccessController implements Serviceable
059{
060    private static final List<String> __CREATOR_RIGHTS = List.of(
061            "Front_Edition_Access_Right",
062            "Workflow_Rights_Edition_Online",
063            "CMS_Rights_DeleteContent",
064            "Workflow_Rights_Validate"
065    );
066    
067    /** ContentTypes Helper */
068    protected ContentTypesHelper _cTypeHelper;
069    /** The ametys object resolver */
070    protected AmetysObjectResolver _resolver;
071
072    public void service(ServiceManager smanager) throws ServiceException
073    {
074        _cTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE);
075        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
076    }
077    
078    public boolean isSupported(Object object)
079    {
080        return object instanceof Content && _cTypeHelper.isInstanceOf((Content) object, UGCConstants.UGC_MIXIN_TYPE);
081    }
082    
083    public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object)
084    {
085        if (object instanceof Content && ((Content) object).getCreator().equals(user))
086        {
087            return __CREATOR_RIGHTS.contains(rightId) ? AccessResult.USER_ALLOWED : AccessResult.UNKNOWN;
088        }
089        
090        return AccessResult.UNKNOWN;
091    }
092
093    public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
094    {
095        return AccessResult.UNKNOWN;
096    }
097
098    /**
099     * If creator, access to a list of rights
100     */
101    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
102    {
103        Map<String, AccessResult> permissionByRight = new HashMap<>();
104        
105        if (((Content) object).getCreator().equals(user))
106        {
107            for (String rightId : __CREATOR_RIGHTS)
108            {
109                permissionByRight.put(rightId, AccessResult.USER_ALLOWED);
110            }
111        }
112        
113        return permissionByRight;
114    }
115
116    public AccessResult getPermissionForAnonymous(String rightId, Object object)
117    {
118        return AccessResult.UNKNOWN;
119    }
120
121    public AccessResult getReadAccessPermissionForAnonymous(Object object)
122    {
123        return AccessResult.UNKNOWN;
124    }
125
126    public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object)
127    {
128        return AccessResult.UNKNOWN;
129    }
130
131    public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object)
132    {
133        return AccessResult.UNKNOWN;
134    }
135
136    /**
137     * If right requested is in the list, the creator is added the list of USER_ALLOWED
138     */
139    public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object)
140    {
141        Map<UserIdentity, AccessResult> permissionByUser = new HashMap<>();
142        
143        if (__CREATOR_RIGHTS.contains(rightId))
144        {
145            permissionByUser.put(((Content) object).getCreator(), AccessResult.USER_ALLOWED);
146        }
147        return permissionByUser;
148    }
149
150    public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object)
151    {
152        return MapUtils.EMPTY_MAP;
153    }
154
155    public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object)
156    {
157        return MapUtils.EMPTY_MAP;
158    }
159
160    public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object)
161    {
162        return MapUtils.EMPTY_MAP;
163    }
164
165    public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId)
166    {
167        return false;
168    }
169
170    public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups)
171    {
172        return false;
173    }
174
175    public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
176    {
177        return false;
178    }
179
180    public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
181    {
182        return false;
183    }
184
185    public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
186    {
187        return false;
188    }
189
190    public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
191    {
192        return false;
193    }
194    
195    @Override
196    public AccessExplanation getStandardAccessExplanation(AccessResult permission, Object object)
197    {
198        switch (permission)
199        {
200            case USER_ALLOWED:
201            case UNKNOWN:
202                return new AccessExplanation(
203                        getId(),
204                        permission,
205                        new I18nizableText("plugin.ugc", "PLUGINS_UGC_CREATOR_ACCESS_CONTROLLER_" + permission.name() + "_EXPLANATION",
206                                Map.of("title", new I18nizableText(((Content) object).getTitle()))
207                                )
208                        );
209            default:
210                return super.getStandardAccessExplanation(permission, object);
211        }
212    }
213    
214    public I18nizableText getObjectLabel(Object object)
215    {
216        if (object instanceof Content content)
217        {
218            return new I18nizableText(content.getTitle());
219        }
220        throw new RightsException("Unsupported context: " + object.toString());
221    }
222
223    public I18nizableText getObjectCategory(Object object)
224    {
225        return ContentAccessController.CONTENT_CONTEXT_CATEGORY;
226    }
227
228    @Override
229    protected Iterable< ? extends Object> getHandledObjects(UserIdentity identity, Set<GroupIdentity> groups)
230    {
231        String siteName = WebHelper.getSiteName(ContextHelper.getRequest(_context));
232        
233        if (StringUtils.isNotBlank(siteName))
234        {
235            Expression typeExpr = new ContentTypeOrMixinExpression(Operator.EQ, UGCConstants.UGC_MIXIN_TYPE);
236            Expression userExpression = new UserExpression(DefaultContent.METADATA_CREATOR, Operator.EQ, identity);
237            Expression siteExpression = new StringExpression(SiteAwareAmetysObject.METADATA_SITE, Operator.EQ, siteName);
238            String query = ContentQueryHelper.getContentXPathQuery(new AndExpression(typeExpr, userExpression, siteExpression));
239            return _resolver.query(query);
240        }
241        return List.of();
242    }
243    
244    @Override
245    protected Collection<String> getHandledRights(UserIdentity identity, Set<GroupIdentity> groups, Object object)
246    {
247        return __CREATOR_RIGHTS;
248    }
249}