001/*
002 *  Copyright 2025 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.configuration.Configurable;
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030
031import org.ametys.cms.contenttype.ContentType;
032import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
033import org.ametys.cms.contenttype.ContentTypesHelper;
034import org.ametys.cms.repository.Content;
035import org.ametys.cms.rights.ContentAccessController;
036import org.ametys.core.group.GroupIdentity;
037import org.ametys.core.right.AccessController;
038import org.ametys.core.right.AccessController.Permission.PermissionType;
039import org.ametys.core.right.AccessExplanation;
040import org.ametys.core.right.RightManager;
041import org.ametys.core.right.RightManager.RightResult;
042import org.ametys.core.right.RightsException;
043import org.ametys.core.user.UserIdentity;
044import org.ametys.runtime.i18n.I18nizableText;
045import org.ametys.runtime.plugin.component.PluginAware;
046
047/**
048 * Access controller for odf content that don't have an edition right, the edition right will be checked based on the creation right (example : skills).
049 * 
050 * <pre>
051 * Configuration needed :
052 * - 'content-type' : The content type
053 *    -> The creation right will be retrieved from the content type definition
054 * - 'i18n-keys-prefix' : The prefix of the i18n keys that will be used :
055 *    -> <b>i18n-keys-prefix</b>_USER_ALLOWED_EXPLANATION
056 *    -> <b>i18n-keys-prefix</b>_ANY_CONNECTED_ALLOWED_EXPLANATION
057 *    -> <b>i18n-keys-prefix</b>_UNKNOWN_EXPLANATION
058 *    -> <b>i18n-keys-prefix</b>_CONTENT_EXPLANATION_LABEL
059 *    -> <b>i18n-keys-prefix</b>_ALL_CONTENTS_EXPLANATION_LABEL
060 *    -> <b>i18n-keys-prefix</b>_ALL_CONTENTS_LABEL
061 * </pre>
062 */
063public class EditContentWithCreationRightAccessController implements AccessController, Serviceable, Configurable, PluginAware
064{
065    /** Fake context to check on all contents */
066    private static final String __ALL_CONTENTS_FAKE_CONTEXT = "$allContents$";
067    
068    private static List<String> _RIGHT_IDS = List.of("Workflow_Rights_Edition_Online");
069    
070    /** The contenttype to consider */
071    protected String _contentType;
072    /** The creation right found in the contenttype */
073    protected String _creationRight;
074    /** The prefix of the i18n keys to use to explain rights */
075    protected String _i18nKeysPrefix;
076    
077    /** The content type extension point */
078    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
079    /** The content types helper */
080    protected ContentTypesHelper _contentTypesHelper;
081    /** The right manager */
082    protected RightManager _rightManager;
083
084    private String _pluginName;
085    private String _id;
086    
087    public void service(ServiceManager manager) throws ServiceException
088    {
089        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
090        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
091        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
092    }
093    
094    public void configure(Configuration configuration) throws ConfigurationException
095    {
096        _contentType = configuration.getChild("content-type").getValue();
097        if (!_contentTypeExtensionPoint.hasExtension(_contentType))
098        {
099            throw new ConfigurationException("The content type '" + _contentType + "' is not registered in the content type extension point.");
100        }
101        else
102        {
103            ContentType contentType = _contentTypeExtensionPoint.getExtension(_contentType);
104            _creationRight = contentType.getRight();
105            
106            if (_creationRight == null)
107            {
108                throw new ConfigurationException("The content type '" + _contentType + "' has not creation right in its definition.");
109            }
110        }
111        
112        
113        _i18nKeysPrefix = configuration.getChild("i18n-keys-prefix").getValue();
114    }
115    
116    public boolean supports(Object object)
117    {
118        return object instanceof Content content && _contentTypesHelper.isInstanceOf(content, _contentType);
119    }
120    
121    public void setPluginInfo(String pluginName, String featureName, String id)
122    {
123        _pluginName = pluginName;
124        _id = id;
125    }
126    
127    public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object)
128    {
129        if (_RIGHT_IDS.contains(rightId))
130        {
131            return _rightManager.hasRight(user, _creationRight, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW ? AccessResult.USER_ALLOWED : AccessResult.UNKNOWN;
132        }
133        
134        return AccessResult.UNKNOWN;
135    }
136
137    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
138    {
139        Map<String, AccessResult> permissionByRight = new HashMap<>();
140        
141        if (_rightManager.hasRight(user, _creationRight, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW)
142        {
143            _RIGHT_IDS.stream()
144                .forEach(r -> {
145                    permissionByRight.put(r, AccessResult.USER_ALLOWED);
146                });
147        }
148        
149        return permissionByRight;
150    }
151    
152    public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object)
153    {
154        Map<UserIdentity, AccessResult> permissionByUser = new HashMap<>();
155        
156        if (_RIGHT_IDS.contains(rightId))
157        {
158            for (UserIdentity user : _rightManager.getAllowedUsers(_creationRight, "/${WorkspaceName}").getAllowedUsers())
159            {
160                permissionByUser.put(user, AccessResult.USER_ALLOWED);
161            }
162        }
163        return permissionByUser;
164    }
165    
166    public AccessExplanation explainReadAccessPermissionForAnyConnectedUser(Object object)
167    {
168        return _getAccessExplanation(getReadAccessPermissionForAnyConnectedUser(object), object);
169    }
170    
171    public AccessExplanation explainReadAccessPermission(UserIdentity user, Set<GroupIdentity> groups, Object object)
172    {
173        return _getAccessExplanation(getReadAccessPermission(user, groups, object), object);
174    }
175    
176    public AccessExplanation explainPermission(UserIdentity user, Set<GroupIdentity> groups, String rightId, Object object)
177    {
178        return _getAccessExplanation(getPermission(user, groups, rightId, object), object);
179    }
180    
181    private AccessExplanation _getAccessExplanation(AccessResult permission, Object object)
182    {
183        switch (permission)
184        {
185            case USER_ALLOWED:
186            case ANY_CONNECTED_ALLOWED:
187            case UNKNOWN:
188                return new AccessExplanation(getId(), permission,
189                        new I18nizableText("plugin." + _pluginName, _i18nKeysPrefix + "_" + permission.name() + "_EXPLANATION",
190                                Map.of(
191                                        "objectLabel", getObjectLabelForExplanation(object)
192                                    )
193                                )
194                        );
195            default:
196                return AccessController.getDefaultAccessExplanation(getId(), permission);
197        }
198    }
199    
200    private I18nizableText getObjectLabelForExplanation(Object object)
201    {
202        if (object.equals(__ALL_CONTENTS_FAKE_CONTEXT))
203        {
204            return new I18nizableText("plugin." + _pluginName, _i18nKeysPrefix + "_ALL_CONTENTS_EXPLANATION_LABEL");
205        }
206        else if (object instanceof Content content)
207        {
208            return new I18nizableText(
209                    "plugin." + _pluginName,
210                    _i18nKeysPrefix + "_CONTENT_EXPLANATION_LABEL",
211                    Map.of("title", new I18nizableText(content.getTitle()))
212                );
213        }
214        throw new RightsException("Unsupported context: " + object.toString());
215    }
216    
217    @Override
218    public I18nizableText getObjectLabel(Object object)
219    {
220        if (object.equals(__ALL_CONTENTS_FAKE_CONTEXT))
221        {
222            return new I18nizableText("plugin." + _pluginName, _i18nKeysPrefix + "_ALL_CONTENTS_LABEL");
223        }
224        return new I18nizableText(((Content) object).getTitle());
225    }
226    
227    @Override
228    public Map<ExplanationObject, Map<Permission, AccessExplanation>> explainAllPermissions(UserIdentity identity, Set<GroupIdentity> groups, Set<Object> workspacesContexts)
229    {
230        // Do not list all the contents.
231        // Instead take advantage of the fact that the getPermission methods
232        // ignore the context to call them with a fake context to generate a explanation for "all the content of the type"
233        Map<Permission, AccessExplanation> contextPermissions = new HashMap<>();
234        for (String rightId: _RIGHT_IDS)
235        {
236            AccessExplanation explanation = explainPermission(identity, groups, rightId, __ALL_CONTENTS_FAKE_CONTEXT);
237            if (explanation.accessResult() != AccessResult.UNKNOWN)
238            {
239                contextPermissions.put(new Permission(PermissionType.RIGHT, rightId), explanation);
240            }
241        }
242        
243        return Map.of(getExplanationObject(__ALL_CONTENTS_FAKE_CONTEXT), contextPermissions);
244    }
245    
246    public Map<Permission, AccessExplanation> explainAllPermissionsForAnyConnected(Object object)
247    {
248        return Collections.EMPTY_MAP;
249    }
250    
251    public Map<UserIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByUser(Object object)
252    {
253        Map<UserIdentity, Map<Permission, AccessExplanation>> permissionsByUser = new HashMap<>();
254        
255        // All user have the same set of permission. Compute it once and for all
256        Map<Permission, AccessExplanation> userPermissions = new HashMap<>();
257        AccessExplanation explanation = _getAccessExplanation(AccessResult.USER_ALLOWED, object);
258        for (String right: _RIGHT_IDS)
259        {
260            Permission permission = new Permission(PermissionType.RIGHT, right);
261            userPermissions.put(permission, explanation);
262        }
263        
264        for (UserIdentity user : _rightManager.getAllowedUsers(_creationRight, "/${WorkspaceName}").getAllowedUsers())
265        {
266            permissionsByUser.put(user, userPermissions);
267        }
268        
269        return permissionsByUser;
270    }
271    
272    @Override
273    public I18nizableText getObjectCategory(Object object)
274    {
275        return ContentAccessController.CONTENT_CONTEXT_CATEGORY;
276    }
277    
278    public int getObjectPriority(Object object)
279    {
280        if (object.equals(__ALL_CONTENTS_FAKE_CONTEXT))
281        {
282            return 5;
283        }
284        return AccessController.super.getObjectPriority(object);
285    }
286    
287    public String getId()
288    {
289        return _id;
290    }
291    
292    public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
293    {
294        return AccessResult.UNKNOWN;
295    }
296
297    public AccessResult getReadAccessPermissionForAnonymous(Object object)
298    {
299        return AccessResult.UNKNOWN;
300    }
301
302    public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object)
303    {
304        return AccessResult.UNKNOWN;
305    }
306
307    public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object)
308    {
309        return Collections.EMPTY_MAP;
310    }
311
312    public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object)
313    {
314        return Collections.EMPTY_MAP;
315    }
316    
317    public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups)
318    {
319        return false;
320    }
321
322    public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
323    {
324        return false;
325    }
326
327    public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
328    {
329        return false;
330    }
331
332    public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object)
333    {
334        return AccessResult.UNKNOWN;
335    }
336
337    public AccessResult getPermissionForAnonymous(String rightId, Object object)
338    {
339        return AccessResult.UNKNOWN;
340    }
341
342    public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object)
343    {
344        return Collections.EMPTY_MAP;
345    }
346
347    public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId)
348    {
349        return false;
350    }
351
352    public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
353    {
354        return false;
355    }
356
357    public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
358    {
359        return false;
360    }
361    
362    public Map<Permission, AccessExplanation> explainAllPermissionsForAnonymous(Object object)
363    {
364        return Map.of();
365    }
366    
367    public Map<GroupIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByGroup(Object object)
368    {
369        return Map.of();
370    }
371}