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