001/*
002 *  Copyright 2026 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.forms.rights;
017
018import java.util.HashMap;
019import java.util.List;
020import java.util.Map;
021import java.util.Set;
022import java.util.stream.Collectors;
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.core.group.GroupIdentity;
029import org.ametys.core.right.AccessController;
030import org.ametys.core.right.AccessController.Permission.PermissionType;
031import org.ametys.core.right.AccessExplanation;
032import org.ametys.core.right.AllowedUsers;
033import org.ametys.core.right.RightManager;
034import org.ametys.core.right.RightManager.ContextPermissions;
035import org.ametys.core.right.RightManager.RightResult;
036import org.ametys.core.right.RightsException;
037import org.ametys.core.user.UserIdentity;
038import org.ametys.plugins.forms.dao.FormDAO;
039import org.ametys.plugins.forms.dao.FormEntryDAO;
040import org.ametys.plugins.forms.repository.Form;
041import org.ametys.plugins.forms.repository.FormEntry;
042import org.ametys.plugins.repository.AmetysObjectResolver;
043import org.ametys.runtime.i18n.I18nizableText;
044import org.ametys.runtime.plugin.component.PluginAware;
045
046/**
047 * {@link AccessController} to allow access to a {@link FormEntry}
048 * For the read permission, it checks the defined read right on the parent form.
049 * For the other permissions, it checks the right on the parent form.
050 */
051public class FormEntryAccessController implements AccessController, Serviceable, PluginAware
052{
053    private static final String __READ_RIGHT = FormEntryDAO.HANDLE_FORMS_ENTRIES_RIGHT_ID;
054    
055    /** The right manager */
056    protected RightManager _rightManager;
057    
058    /** The form DAO */
059    protected FormDAO _formDAO;
060    
061    /** The ametys object resolver */
062    protected AmetysObjectResolver _resolver;
063    
064    /** The access controller id */
065    protected String _id;
066    
067    public void service(ServiceManager manager) throws ServiceException
068    {
069        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
070        _formDAO = (FormDAO) manager.lookup(FormDAO.ROLE);
071        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
072    }
073
074    public void setPluginInfo(String pluginName, String featureName, String id)
075    {
076        _id = id;
077    }
078    
079    public String getId()
080    {
081        return _id;
082    }
083    
084    public boolean supports(Object element)
085    {
086        return element instanceof FormEntry;
087    }
088    
089    public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object)
090    {
091        Form form = ((FormEntry) object).getForm();
092        RightResult anonymousRight = _rightManager.hasAnonymousRight(rightId, form);
093        if (anonymousRight == RightResult.RIGHT_ALLOW)
094        {
095            return AccessResult.ANONYMOUS_ALLOWED;
096        }
097        else
098        {
099            RightResult hasRight = _rightManager.hasRight(user, rightId, form);
100            if (hasRight == RightResult.RIGHT_DENY)
101            {
102                return AccessResult.USER_DENIED;
103            }
104            
105            RightResult anyConnectedUserRight = _rightManager.hasAnyConnectedUserRight(rightId, form);
106            if (anyConnectedUserRight == RightResult.RIGHT_ALLOW)
107            {
108                return AccessResult.ANY_CONNECTED_ALLOWED;
109            }
110            else if (hasRight == RightResult.RIGHT_ALLOW)
111            {
112                return AccessResult.USER_ALLOWED;
113            }
114            else if (anyConnectedUserRight == RightResult.RIGHT_DENY)
115            {
116                return AccessResult.ANY_CONNECTED_DENIED;
117            }
118            else if (anonymousRight == RightResult.RIGHT_DENY)
119            {
120                return AccessResult.ANONYMOUS_DENIED;
121            }
122        }
123        
124        return AccessResult.UNKNOWN;
125    }
126    
127    public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
128    {
129        return getPermission(user, userGroups, __READ_RIGHT, object);
130    }
131
132    public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object)
133    {
134        Form form = ((FormEntry) object).getForm();
135        
136        Map<String, AccessResult> permissionByRight = new HashMap<>();
137        
138        _rightManager.getUserRights(user, form).stream().forEach(rightId -> {
139            permissionByRight.put(rightId, AccessResult.USER_ALLOWED);
140        });
141        
142        return permissionByRight;
143
144    }
145
146    public AccessResult getPermissionForAnonymous(String rightId, Object object)
147    {
148        Form form = ((FormEntry) object).getForm();
149        RightResult anonymousRight = _rightManager.hasAnonymousRight(rightId, form);
150        switch (anonymousRight)
151        {
152            case RIGHT_ALLOW:
153                return AccessResult.ANONYMOUS_ALLOWED;
154            case RIGHT_DENY:
155                return AccessResult.ANONYMOUS_DENIED;
156            default:
157                return AccessResult.UNKNOWN;
158        }
159    }
160    
161    public AccessResult getReadAccessPermissionForAnonymous(Object object)
162    {
163        return getPermissionForAnonymous(__READ_RIGHT, object);
164    }
165
166    public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object)
167    {
168        Form form = ((FormEntry) object).getForm();
169        RightResult anonymousRight = _rightManager.hasAnonymousRight(rightId, form);
170        if (anonymousRight == RightResult.RIGHT_ALLOW)
171        {
172            return AccessResult.ANONYMOUS_ALLOWED;
173        }
174        else
175        {
176            RightResult anyConnectedUserRight = _rightManager.hasAnyConnectedUserRight(rightId, form);
177            switch (anyConnectedUserRight)
178            {
179                case RIGHT_ALLOW:
180                    return AccessResult.ANY_CONNECTED_ALLOWED;
181                case RIGHT_DENY:
182                    return AccessResult.ANY_CONNECTED_DENIED;
183                default:
184                    return anonymousRight == RightResult.RIGHT_DENY ? AccessResult.ANONYMOUS_DENIED : AccessResult.UNKNOWN;
185            }
186        }
187    }
188    
189    public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object)
190    {
191        return getPermissionForAnyConnectedUser(__READ_RIGHT, object);
192    }
193
194    public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object)
195    {
196        Form form = ((FormEntry) object).getForm();
197        Map<UserIdentity, AccessResult> permissionsByUser = new HashMap<>();
198        
199        AllowedUsers allowedUsers = _rightManager.getAllowedUsers(rightId, form);
200        allowedUsers.getAllowedUsers().forEach(user -> {
201            permissionsByUser.put(user, AccessResult.USER_ALLOWED);
202        });
203        
204        allowedUsers.getDeniedUsers().forEach(user -> {
205            permissionsByUser.put(user, AccessResult.USER_DENIED);
206        });
207        
208        return permissionsByUser;
209    }
210
211    public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object)
212    {
213        return getPermissionByUser(__READ_RIGHT, object);
214    }
215    
216    public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object)
217    {
218        Form form = ((FormEntry) object).getForm();
219        Map<GroupIdentity, AccessResult> permissionsByGroup = new HashMap<>();
220        
221        AllowedUsers allowedUsers = _rightManager.getAllowedUsers(rightId, form);
222        allowedUsers.getAllowedGroups().forEach(group -> {
223            permissionsByGroup.put(group, AccessResult.USER_ALLOWED);
224        });
225        
226        allowedUsers.getAllowedGroups().forEach(group -> {
227            permissionsByGroup.put(group, AccessResult.USER_DENIED);
228        });
229        
230        return permissionsByGroup;
231    }
232
233    public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object)
234    {
235        return getPermissionByGroup(__READ_RIGHT, object);
236    }
237    
238    public Map<Permission, AccessExplanation> explainAllPermissionsForAnonymous(Object object)
239    {
240        FormEntry entry = (FormEntry) object;
241        
242        ContextPermissions explainAllPermissions = _rightManager.explainAllPermissions(entry.getForm());
243        Map<Permission, AccessExplanation> permissionsForAnonymous = _transformPermissionsByAccessExplanation(entry, explainAllPermissions.permissionsForAnonymous());
244        
245        AccessResult accessResult = getReadAccessPermissionForAnonymous(object);
246        permissionsForAnonymous.put(new Permission(PermissionType.READ, null), _getReadAccessExplanation(accessResult, object));
247        
248        return permissionsForAnonymous;
249    }
250
251    public Map<Permission, AccessExplanation> explainAllPermissionsForAnyConnected(Object object)
252    {
253        FormEntry entry = (FormEntry) object;
254        
255        ContextPermissions explainAllPermissions = _rightManager.explainAllPermissions(entry.getForm());
256        Map<Permission, AccessExplanation> permissionsForAnyConnected = _transformPermissionsByAccessExplanation(entry, explainAllPermissions.permissionsForAnyConnected());
257        
258        AccessResult accessResult = getReadAccessPermissionForAnonymous(object);
259        permissionsForAnyConnected.put(new Permission(PermissionType.READ, null), _getReadAccessExplanation(accessResult, object));
260        
261        return permissionsForAnyConnected;
262    }
263
264    public Map<UserIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByUser(Object object)
265    {
266        FormEntry entry = (FormEntry) object;
267        
268        ContextPermissions explainAllPermissions = _rightManager.explainAllPermissions(entry.getForm());
269        Map<UserIdentity, Map<Permission, AccessExplanation>> results = explainAllPermissions.permissionsByUser()
270                .entrySet()
271                .stream()
272                .collect(Collectors.toMap(e -> e.getKey(), e -> _transformPermissionsByAccessExplanation(entry, e.getValue())));
273        
274        Map<UserIdentity, AccessResult> readAccessPermissionByUser = getReadAccessPermissionByUser(object);
275        for (UserIdentity identity: readAccessPermissionByUser.keySet())
276        {
277            AccessResult accessResult = readAccessPermissionByUser.get(identity);
278            AccessExplanation explanation = _getReadAccessExplanation(accessResult, object);
279            Map<Permission, AccessExplanation> userResults = results.computeIfAbsent(identity, __ -> new HashMap<>());
280            userResults.put(new Permission(PermissionType.READ, null), explanation);
281        }
282        
283        return results;
284    }
285
286    public Map<GroupIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByGroup(Object object)
287    {
288        FormEntry entry = (FormEntry) object;
289        
290        ContextPermissions explainAllPermissions = _rightManager.explainAllPermissions(entry.getForm());
291        Map<GroupIdentity, Map<Permission, AccessExplanation>> results = explainAllPermissions.permissionsByGroup()
292                .entrySet()
293                .stream()
294                .collect(Collectors.toMap(e -> e.getKey(), e -> _transformPermissionsByAccessExplanation(entry, e.getValue())));
295        
296        Map<GroupIdentity, AccessResult> readAccessPermissionByGroup = getReadAccessPermissionByGroup(object);
297        for (GroupIdentity identity: readAccessPermissionByGroup.keySet())
298        {
299            AccessResult accessResult = readAccessPermissionByGroup.get(identity);
300            AccessExplanation explanation = _getReadAccessExplanation(accessResult, object);
301            Map<Permission, AccessExplanation> groupResults = results.computeIfAbsent(identity, __ -> new HashMap<>());
302            groupResults.put(new Permission(PermissionType.READ, null), explanation);
303        }
304        
305        return results;
306    }
307    
308    private Map<Permission, AccessExplanation> _transformPermissionsByAccessExplanation(FormEntry formEntry, Map<Permission, List<AccessExplanation>> permissionsByRightExplanation)
309    {
310        Map<Permission, AccessExplanation> result = new HashMap<>();
311        for (Permission permission : permissionsByRightExplanation.keySet())
312        {
313            // For non read permission, we merge all explanations into one explanation with merged access result
314            if (permission.type() != PermissionType.READ)
315            {
316                List<AccessExplanation> explanations = permissionsByRightExplanation.get(permission);
317                List<AccessResult> accessResults = explanations.stream()
318                        .map(AccessExplanation::accessResult)
319                        .toList();
320                
321                AccessResult accessResult = AccessResult.merge(accessResults);
322                result.put(permission, _getFormReadAccessExplanation(accessResult, formEntry));
323            }
324        }
325        return result;
326    }
327    
328    private AccessExplanation _getFormReadAccessExplanation(AccessResult result, Object object)
329    {
330        switch (result)
331        {
332            case USER_ALLOWED:
333            case USER_DENIED:
334            case ANONYMOUS_ALLOWED:
335            case ANONYMOUS_DENIED:
336            case ANY_CONNECTED_ALLOWED:
337            case ANY_CONNECTED_DENIED:
338                return new AccessExplanation(
339                        getId(), 
340                        result,
341                        new I18nizableText("plugin.forms", "PLUGINS_FORM_ENTRY_FROM_FORM_ACCESS_CONTROLLER_" + result.name() + "_EXPLANATION", Map.of("title", getObjectLabel(object)))
342                        );
343            default:
344                return AccessController.getDefaultAccessExplanation(getId(), result);
345        }
346        
347    }
348    
349    private AccessExplanation _getReadAccessExplanation(AccessResult result, Object object)
350    {
351        switch (result)
352        {
353            case USER_ALLOWED:
354            case USER_DENIED:
355            case ANONYMOUS_ALLOWED:
356            case ANONYMOUS_DENIED:
357            case ANY_CONNECTED_ALLOWED:
358            case ANY_CONNECTED_DENIED:
359                return new AccessExplanation(
360                        getId(), 
361                        result,
362                        new I18nizableText("plugin.forms", "PLUGINS_FORM_ENTRY_ACCESS_CONTROLLER_" + result.name() + "_EXPLANATION", Map.of("title", getObjectLabel(object)))
363                        );
364            default:
365                return AccessController.getDefaultAccessExplanation(getId(), result);
366        }
367    }
368    
369    public I18nizableText getObjectLabel(Object object)
370    {
371        if (object instanceof FormEntry formEntry)
372        {
373            Form form = formEntry.getForm();
374            return new I18nizableText(FormAccessController.getFormDirectoryPathLabel(form.getParent()) + form.getTitle());
375        }
376        throw new RightsException("Unsupported context: " + object.toString());
377    }
378
379    public I18nizableText getObjectCategory(Object object)
380    {
381        return FormAccessController.FORM_CONTEXT_CATEGORY;
382    }
383    
384    // ************************************************************************************************************************************************************
385    // *** No workspace permission
386    // ************************************************************************************************************************************************************
387    
388    public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups)
389    {
390        return false;
391    }
392    
393    public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
394    {
395        return false;
396    }
397
398    public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts)
399    {
400        return false;
401    }
402
403    public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId)
404    {
405        return false;
406    }
407
408    public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
409    {
410        return false;
411    }
412
413    public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId)
414    {
415        return false;
416    }
417    
418    public Map<ExplanationObject, Map<Permission, AccessExplanation>> explainAllPermissions(UserIdentity identity, Set<GroupIdentity> groups, Set<Object> workspacesContexts)
419    {
420        return Map.of();
421    }
422}