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.plugins.repository.jcr;
017
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Map;
022import java.util.Set;
023import java.util.stream.Collectors;
024
025import javax.jcr.Node;
026import javax.jcr.NodeIterator;
027import javax.jcr.PathNotFoundException;
028import javax.jcr.Repository;
029import javax.jcr.RepositoryException;
030import javax.jcr.Session;
031import javax.jcr.Value;
032import javax.jcr.lock.Lock;
033import javax.jcr.lock.LockManager;
034import javax.jcr.query.Query;
035
036import org.apache.avalon.framework.component.Component;
037import org.apache.avalon.framework.service.ServiceException;
038import org.apache.avalon.framework.service.ServiceManager;
039import org.apache.avalon.framework.service.Serviceable;
040import org.apache.jackrabbit.util.ISO9075;
041import org.apache.jackrabbit.util.Text;
042
043import org.ametys.core.group.GroupIdentity;
044import org.ametys.core.user.UserIdentity;
045import org.ametys.core.util.LambdaUtils;
046import org.ametys.plugins.repository.ACLAmetysObject;
047import org.ametys.plugins.repository.AmetysObjectResolver;
048import org.ametys.plugins.repository.AmetysRepositoryException;
049import org.ametys.plugins.repository.ModifiableACLAmetysObject;
050import org.ametys.plugins.repository.ModifiableACLAmetysObjectProfileAssignmentStorage;
051import org.ametys.plugins.repository.RepositoryConstants;
052import org.ametys.plugins.repository.provider.AbstractRepository;
053import org.ametys.plugins.repository.query.expression.Expression;
054import org.ametys.plugins.repository.query.expression.OrExpression;
055
056/**
057 * Helper for implementing {@link ModifiableACLAmetysObject} in JCR under its node.
058 */
059public class ACLJCRAmetysObjectHelper implements Component, Serviceable
060{
061    /** The AmetysObject resolver */
062    protected static AmetysObjectResolver _resolver;
063    /** The repository */
064    protected static Repository _repository;
065    
066    private static final String __NODE_NAME_ROOT_ACL = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":acl";
067    private static final String __NODETYPE_ROOT_ACL = RepositoryConstants.NAMESPACE_PREFIX + ":acl";
068    
069    private static final String __NODE_NAME_ACL_USERS = "users";
070    private static final String __NODE_NAME_ACL_GROUPS = "groups";
071    private static final String __NODETYPE_ACL_USER = RepositoryConstants.NAMESPACE_PREFIX + ":acl-user";
072    private static final String __NODETYPE_ACL_GROUP = RepositoryConstants.NAMESPACE_PREFIX + ":acl-group";
073    private static final String __NODETYPE_UNSTRUCTURED = "nt:unstructured";
074    
075    private static final String __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":allowed-any-connected-profiles";
076    private static final String __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":denied-any-connected-profiles";
077    private static final String __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":allowed-anonymous-profiles";
078    private static final String __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":denied-anonymous-profiles";
079    
080    private static final String __PROPERTY_NAME_ALLOWED_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":allowed-profiles";
081    private static final String __PROPERTY_NAME_DENIED_PROFILES = RepositoryConstants.NAMESPACE_PREFIX + ":denied-profiles";
082    
083    private static final String __PROPERTY_NAME_DISALLOW_INHERITANCE = RepositoryConstants.NAMESPACE_PREFIX + ":disallow-inheritance";
084    
085    @Override
086    public void service(ServiceManager manager) throws ServiceException
087    {
088        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
089        _repository = (Repository) manager.lookup(AbstractRepository.ROLE);
090    }
091    
092    
093    /* -------------- */
094    /* HAS PERMISSION */
095    /* -------------- */
096    
097    private static Set<String> _convertNodeToPath(Set<? extends Object> rootNodes)
098    {
099        return rootNodes.stream().map(JCRAmetysObject.class::cast).map(LambdaUtils.wrap(ao -> ISO9075.encodePath(ao.getNode().getPath()))).collect(Collectors.toSet());
100    }
101    
102    /**
103     * Returns true if any ACL Ametys object has one of the given profiles as denied for the user
104     * @param user The user
105     * @param profileIds The ids of the profiles
106     * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
107     * @return true if any ACL Ametys object has one of the given profiles as denied for the user
108     */
109    public static boolean hasUserDeniedProfile(Set<? extends Object> rootNodes, UserIdentity user, Set<String> profileIds)
110    {
111        Expression expr = new DeniedProfileExpression(profileIds.toArray(new String[profileIds.size()]));
112        for (String rootPath : _convertNodeToPath(rootNodes))
113        {
114            NodeIterator nodes = getACLUsers(user, rootPath, expr);
115            
116            if (nodes.hasNext())
117            {
118                return true;
119            }
120        }
121        return false;
122    }
123    
124    /**
125     * Returns true if any ACL Ametys object has one of the given profiles as allowed for the user
126     * @param user The user
127     * @param profileIds The ids of the profiles
128     * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
129     * @return true if any ACL Ametys object has one of the given profiles as allowed for the user
130     */
131    public static boolean hasUserAllowedProfile(Set<? extends Object> rootNodes, UserIdentity user, Set<String> profileIds)
132    {
133        Expression expr = new AllowedProfileExpression(profileIds.toArray(new String[profileIds.size()]));
134        for (String rootPath : _convertNodeToPath(rootNodes))
135        {
136            NodeIterator nodes = getACLUsers(user, rootPath, expr);
137            
138            if (nodes.hasNext())
139            {
140                return true;
141            }
142        }
143        return false;
144    }
145    
146    /**
147     * Returns true if any ACL Ametys object has one of the given profiles as denied for the group
148     * @param group The group
149     * @param profileIds The ids of the profiles
150     * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
151     * @return true if any ACL Ametys object has one of the given profiles as denied for the group
152     */
153    public static boolean hasGroupDeniedProfile(Set<? extends Object> rootNodes, GroupIdentity group, Set<String> profileIds)
154    {
155        Expression expr = new DeniedProfileExpression(profileIds.toArray(new String[profileIds.size()]));
156        for (String rootPath : _convertNodeToPath(rootNodes))
157        {
158            NodeIterator nodes = getACLGroups(group, rootPath, expr);
159            
160            if (nodes.hasNext())
161            {
162                return true;
163            }
164        }
165        return false;
166    }
167    
168    /**
169     * Returns true if any ACL Ametys object has one of the given profiles as allowed for the group
170     * @param group The group
171     * @param profileIds The ids of the profiles
172     * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
173     * @return true if any ACL Ametys object has one of the given profiles as allowed for the group
174     */
175    public static boolean hasGroupAllowedProfile(Set<? extends Object> rootNodes, GroupIdentity group, Set<String> profileIds)
176    {
177        Expression expr = new AllowedProfileExpression(profileIds.toArray(new String[profileIds.size()]));
178        for (String rootPath : _convertNodeToPath(rootNodes))
179        {
180            NodeIterator nodes = getACLGroups(group, rootPath, expr);
181            
182            if (nodes.hasNext())
183            {
184                return true;
185            }
186        }
187        return false;
188    }
189    
190    /**
191     * Returns true if any ACL Ametys object has one of the given profiles as denied for any connected user
192     * @param profileIds The ids of the profiles
193     * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
194     * @return true if any ACL Ametys object has one of the given profiles as denied for any connected user
195     */
196    public static boolean hasAnyConnectedDeniedProfile(Set<? extends Object> rootNodes, Set<String> profileIds)
197    {
198        Expression expr = new AnyConnectedDeniedProfileExpression(profileIds.toArray(new String[profileIds.size()]));
199        for (String rootPath : _convertNodeToPath(rootNodes))
200        {
201            NodeIterator nodes = getACLRoots(rootPath, expr);
202            
203            if (nodes.hasNext())
204            {
205                return true;
206            }
207        }
208        return false;
209    }
210    
211    /**
212     * Returns true if any ACL Ametys object has one of the given profiles as allowed for any connected user
213     * @param profileIds The ids of the profiles
214     * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
215     * @return true if any ACL Ametys object has one of the given profiles as allowed for any connected user
216     */
217    public static boolean hasAnyConnectedAllowedProfile(Set<? extends Object> rootNodes, Set<String> profileIds)
218    {
219        Expression expr = new AnyConnectedAllowedProfileExpression(profileIds.toArray(new String[profileIds.size()]));
220        for (String rootPath : _convertNodeToPath(rootNodes))
221        {
222            NodeIterator nodes = getACLRoots(rootPath, expr);
223            
224            if (nodes.hasNext())
225            {
226                return true;
227            }
228        }
229        return false;
230    }
231    
232    /**
233     * Returns true if any ACL Ametys object has one of the given profiles as denied for anonymous
234     * @param profileIds The ids of the profiles
235     * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
236     * @return true if any ACL Ametys object has one of the given profiles as denied for anonymous
237     */
238    public static boolean hasAnonymousDeniedProfile(Set<? extends Object> rootNodes, Set<String> profileIds)
239    {
240        Expression expr = new AnonymousDeniedProfileExpression(profileIds.toArray(new String[profileIds.size()]));
241        for (String rootPath : _convertNodeToPath(rootNodes))
242        {
243            NodeIterator nodes = getACLRoots(rootPath, expr);
244            
245            if (nodes.hasNext())
246            {
247                return true;
248            }
249        }
250        return false;
251    }
252    
253    /**
254     * Returns true if any ACL Ametys object has one of the given profiles as allowed for anonymous
255     * @param profileIds The ids of the profiles
256     * @param rootNodes The JCR root nodes where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
257     * @return true if any ACL Ametys object has one of the given profiles as allowed for anonymous
258     */
259    public static boolean hasAnonymousAllowedProfile(Set<? extends Object> rootNodes, Set<String> profileIds)
260    {
261        Expression expr = new AnonymousAllowedProfileExpression(profileIds.toArray(new String[profileIds.size()]));
262        for (String rootPath : _convertNodeToPath(rootNodes))
263        {
264            NodeIterator nodes = getACLRoots(rootPath, expr);
265            
266            if (nodes.hasNext())
267            {
268                return true;
269            }
270        }
271        return false;
272    }
273    
274    /**
275     * Returns all ACL root objects (ametys:acl nodes)
276     * @param rootPath The root path to restrict the search. Can be null.
277     * @return The ACL root objects
278     */
279    public static NodeIterator getACLRoots (String rootPath)
280    {
281        return getACLRoots(rootPath, null);
282    }
283    
284    /**
285     * Returns all ACL root objects (ametys:acl nodes)
286     * @param rootPath The root path to restrict the search. Can be null.
287     * @param predicat The predicat expression. Can be null.
288     * @return The ACL root objects
289     */
290    public static NodeIterator getACLRoots (String rootPath, Expression predicat)
291    {
292        StringBuilder sb = new StringBuilder("/jcr:root");
293        
294        if (rootPath != null)
295        {
296            sb.append(rootPath);
297        }
298        
299        sb.append("//element(*, ").append(__NODETYPE_ROOT_ACL).append(")");
300        
301        if (predicat != null)
302        {
303            sb.append("[").append(predicat.build()).append("]");
304        }
305        
306        return _query(sb.toString());
307    }
308    
309    /**
310     * Returns all ACL objects for a given user (ametys:acl-user nodes)
311     * @param user The user
312     * @param rootPath The root path to restrict the search. Can be null.
313     * @return The ACL user objects for user
314     */
315    public static NodeIterator getACLUsers (UserIdentity user, String rootPath)
316    {
317        return getACLUsers(user, rootPath, null);
318    }
319    
320    /**
321     * Returns all ACL objects for a given user (ametys:acl-user nodes)
322     * @param user The user
323     * @param rootPath The root path to restrict the search. Can be null.
324     * @param predicat The predicat expression. Can be null.
325     * @return The ACL user objects for user
326     */
327    public static NodeIterator getACLUsers (UserIdentity user, String rootPath, Expression predicat)
328    {
329        StringBuilder sb = new StringBuilder("/jcr:root");
330        
331        if (rootPath != null)
332        {
333            sb.append(rootPath);
334        }
335        
336        sb.append("//element(*, ").append(__NODETYPE_ROOT_ACL).append(")")
337            .append("/").append(__NODE_NAME_ACL_USERS)
338            .append("/").append(user.getPopulationId())
339            .append("/").append(ISO9075.encode(user.getLogin()));
340        
341        if (predicat != null)
342        {
343            sb.append("[").append(predicat.build()).append("]");
344        }
345        
346        String jcrQuery = sb.toString();
347        return _query(jcrQuery);
348    }
349    
350    /**
351     * Returns all ACL objects for users (ametys:acl-user nodes)
352     * @return The ACL user objects for users
353     */
354    public static NodeIterator getACLUsers ()
355    {
356        return getACLUsers(null);
357    }
358    
359    /**
360     * Returns all ACL objects for users (ametys:acl-user nodes)
361     * @param predicat The predicat expression. Can be null.
362     * @return The ACL user objects for users
363     */
364    public static NodeIterator getACLUsers (Expression predicat)
365    {
366        StringBuilder sb = new StringBuilder();
367        
368        sb.append("//element(*, ").append(__NODETYPE_ACL_USER).append(")");
369        
370        if (predicat != null)
371        {
372            sb.append("[").append(predicat.build()).append("]");
373        }
374        
375        return _query(sb.toString());
376    }
377    
378    /**
379     * Returns all ACL objects for groups (ametys:acl-group nodes)
380     * @param predicat The predicat expression. Can be null.
381     * @return The ACL group objects for groups
382     */
383    public static NodeIterator getACLGroups (Expression predicat)
384    {
385        StringBuilder sb = new StringBuilder();
386        
387        sb.append("//element(*, ").append(__NODETYPE_ACL_GROUP).append(")");
388        
389        if (predicat != null)
390        {
391            sb.append("[").append(predicat.build()).append("]");
392        }
393        
394        return _query(sb.toString());
395    }
396    
397    /**
398     * Returns all ACL objects for a given group (ametys:acl-group nodes)
399     * @param group The group
400     * @param rootPath The root path to restrict the search. Can be null.
401     * @return The ACL user objects for groups
402     */
403    public static NodeIterator getACLGroups (GroupIdentity group, String rootPath)
404    {
405        return getACLGroups(group, rootPath, null);
406    }
407    
408    /**
409     * Returns all ACL objects for a given group (ametys:acl-group nodes)
410     * @param group The group
411     * @param rootPath The root path to restrict the search. Can be null.
412     * @param predicat The predicat expression. Can be null.
413     * @return The ACL user objects for groups
414     */
415    public static NodeIterator getACLGroups (GroupIdentity group, String rootPath, Expression predicat)
416    {
417        StringBuilder sb = new StringBuilder("/jcr:root");
418        
419        if (rootPath != null)
420        {
421            sb.append(rootPath);
422        }
423        
424        sb.append("//element(*, ").append(__NODETYPE_ROOT_ACL).append(")")
425            .append("/").append(__NODE_NAME_ACL_GROUPS)
426            .append("/").append(group.getDirectoryId())
427            .append("/").append(ISO9075.encode(Text.escapeIllegalJcrChars(group.getId())));
428        
429        if (predicat != null)
430        {
431            sb.append("[").append(predicat.build()).append("]");
432        }
433        
434        return _query(sb.toString());
435    }
436    
437    /**
438     * Returns the allowed profiles for the user on any ACL Ametys object (and not denied on the same object)
439     * @param user The user
440     * @param rootPath The JCR root path where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
441     * @return the allowed profiles for the user on any ACL Ametys object (and not denied on the same object)
442     */
443    protected static Set<String> _getAllowedProfiles(UserIdentity user, String rootPath)
444    {
445        Set<String> profiles = new HashSet<>();
446        
447        NodeIterator users = getACLUsers(user, rootPath);
448        
449        while (users.hasNext())
450        {
451            Node userNode = (Node) users.next();
452            
453            Set<String> allowedProfiles = _getProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES);
454            Set<String> deniedProfiles = _getProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES);
455            allowedProfiles.removeAll(deniedProfiles); // we want to keep only the not allowed profiles
456            profiles.addAll(allowedProfiles);
457        }
458        
459        return profiles;
460    }
461    
462    /**
463     * Returns the allowed profiles for the group on any ACL Ametys object (and not denied on the same object)
464     * @param group The group
465     * @param rootPath The JCR root path where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
466     * @return Returns the allowed profiles for the group on any ACL Ametys object (and not denied on the same object)
467     */
468    protected static Set<String> _getAllowedProfiles(GroupIdentity group, String rootPath)
469    {
470        Set<String> profiles = new HashSet<>();
471        
472        NodeIterator groups = getACLGroups(group, rootPath);
473        
474        while (groups.hasNext())
475        {
476            Node groupNode = (Node) groups.next();
477            
478            Set<String> allowedProfiles = _getProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES);
479            Set<String> deniedProfiles = _getProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES);
480            allowedProfiles.removeAll(deniedProfiles); // we want to keep only the not allowed profiles
481            profiles.addAll(allowedProfiles);
482        }
483        return profiles;
484    }
485    
486    /**
487     * Returns the allowed profiles for any connected user on any ACL Ametys object (and not denied on the same object)
488     * @param rootPath The JCR root path where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
489     * @return the allowed profiles for any connected user on any ACL Ametys object (and not denied on the same object)
490     */
491    protected static Set<String> _getAnyConnectedAllowedProfiles(String rootPath)
492    {
493        Set<String> profiles = new HashSet<>();
494      
495        NodeIterator objects = getACLRoots(rootPath);
496        while (objects.hasNext())
497        {
498            Node node = (Node) objects.next();
499            
500            Set<String> allowedProfiles = _getProperty(node, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES);
501            Set<String> deniedProfiles = _getProperty(node, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES);
502            allowedProfiles.removeAll(deniedProfiles); // we want to keep only the not allowed profiles
503            profiles.addAll(allowedProfiles);
504        }
505        
506        return profiles;
507    }
508    
509    /**
510     * Returns the allowed profiles for anonymous on any ACL Ametys object (and not denied on the same object)
511     * @param rootPath The JCR root path where starts the query search (must be something like "//element(myNode, ametys:collection)"), it will be the beginning of the JCR query. Can be null to not restrict the search.
512     * @return the allowed profiles for anonymous on any ACL Ametys object (and not denied on the same object)
513     */
514    protected static Set<String>  _getAnonymousAllowedProfiles(String rootPath)
515    {
516        Set<String> profiles = new HashSet<>();
517     
518        NodeIterator objects = getACLRoots(rootPath);
519        while (objects.hasNext())
520        {
521            Node node = (Node) objects.next();
522            
523            Set<String> allowedProfiles = _getProperty(node, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES);
524            Set<String> deniedProfiles = _getProperty(node, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES);
525            allowedProfiles.removeAll(deniedProfiles); // we want to keep only the not allowed profiles
526            profiles.addAll(allowedProfiles);
527        }
528        
529        return profiles;
530    }
531    
532    
533    private static NodeIterator _query (String jcrQuery)
534    {
535        Session session = null;
536        try
537        {
538            session = _repository.login();
539            
540            @SuppressWarnings("deprecation")
541            Query query = session.getWorkspace().getQueryManager().createQuery(jcrQuery, Query.XPATH);
542            return query.execute().getNodes();
543        }
544        catch (RepositoryException ex)
545        {
546            if (session != null)
547            {
548                session.logout();
549            }
550
551            throw new AmetysRepositoryException("An error occured executing the JCR query : " + jcrQuery, ex);
552        }
553    }
554    
555    /* --------------------------------------- */
556    /* ALLOWED PROFILES FOR ANY CONNECTED USER */
557    /* --------------------------------------- */
558    
559    /**
560     * Helper for {@link ACLAmetysObject#getAllowedProfilesForAnyConnectedUser()}
561     * @param node The JCR node for the Ametys object
562     * @return the allowed profiles any connected user has on the given node
563     */
564    public static Set<String> getAllowedProfilesForAnyConnectedUser(Node node)
565    {
566        Node aclNode = _getACLNode(node);
567        if (aclNode == null)
568        {
569            return Collections.EMPTY_SET;
570        }
571        else
572        {
573            return _getProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES);
574        }
575    }
576
577    /**
578     * Helper for {@link ModifiableACLAmetysObject#addAllowedProfilesForAnyConnectedUser(Set)}
579     * @param node The JCR node for the Ametys object
580     * @param profileIds The profiles to add
581     */
582    public static void addAllowedProfilesForAnyConnectedUser(Node node, Set<String> profileIds)
583    {
584        Node aclNode = _getOrCreateACLNode(node);
585        for (String profile : profileIds)
586        {
587            _addProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, profile);
588        }
589        _save(node);
590    }
591
592    /**
593     * Helper for {@link ModifiableACLAmetysObject#removeAllowedProfilesForAnyConnectedUser(Set)}
594     * @param node The JCR node for the Ametys object
595     * @param profileIds The profiles to remove
596     */
597    public static void removeAllowedProfilesForAnyConnectedUser(Node node, Set<String> profileIds)
598    {
599        Node aclNode = _getOrCreateACLNode(node);
600        for (String profile : profileIds)
601        {
602            _removeProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, profile);
603        }
604        _save(node);
605    }
606
607    
608    /* -------------------------------------- */
609    /* DENIED PROFILES FOR ANY CONNECTED USER */
610    /* -------------------------------------- */
611    
612    /**
613     * Helper for {@link ACLAmetysObject#getDeniedProfilesForAnyConnectedUser()}
614     * @param node The JCR node for the Ametys object
615     * @return the denied profiles any connected user has on the given node
616     */
617    public static Set<String> getDeniedProfilesForAnyConnectedUser(Node node)
618    {
619        Node aclNode = _getACLNode(node);
620        if (aclNode == null)
621        {
622            return Collections.EMPTY_SET;
623        }
624        else
625        {
626            return _getProperty(aclNode, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES);
627        }
628    }
629    
630    /**
631     * Helper for {@link ModifiableACLAmetysObject#addDeniedProfilesForAnyConnectedUser(Set)}
632     * @param node The JCR node for the Ametys object
633     * @param profileIds The profiles to add
634     */
635    public static void addDeniedProfilesForAnyConnectedUser(Node node, Set<String> profileIds)
636    {
637        Node aclNode = _getOrCreateACLNode(node);
638        for (String profile : profileIds)
639        {
640            _addProperty(aclNode, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES, profile);
641        }
642        _save(node);
643    }
644    
645    /**
646     * Helper for {@link ModifiableACLAmetysObject#removeDeniedProfilesForAnyConnectedUser(Set)}
647     * @param node The JCR node for the Ametys object
648     * @param profileIds The profiles to remove
649     */
650    public static void removeDeniedProfilesForAnyConnectedUser(Node node, Set<String> profileIds)
651    {
652        Node aclNode = _getOrCreateACLNode(node);
653        for (String profile : profileIds)
654        {
655            _removeProperty(aclNode, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES, profile);
656        }
657        _save(node);
658    }
659    
660    
661    /* ------------------------------ */
662    /* ALLOWED PROFILES FOR ANONYMOUS */
663    /* ------------------------------ */
664    
665    /**
666     * Helper for {@link ACLAmetysObject#getAllowedProfilesForAnonymous()}
667     * @param node The JCR node for the Ametys object
668     * @return the allowed profiles an anonymous user has on the given node
669     */
670    public static Set<String> getAllowedProfilesForAnonymous(Node node)
671    {
672        Node aclNode = _getACLNode(node);
673        if (aclNode == null)
674        {
675            return Collections.EMPTY_SET;
676        }
677        else
678        {
679            return _getProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES);
680        }
681    }
682    
683    /**
684     * Helper for {@link ModifiableACLAmetysObject#addAllowedProfilesForAnonymous(Set)}
685     * @param node The JCR node for the Ametys object
686     * @param profileIds The profiles to add
687     */
688    public static void addAllowedProfilesForAnonymous(Node node, Set<String> profileIds)
689    {
690        Node aclNode = _getOrCreateACLNode(node);
691        for (String profile : profileIds)
692        {
693            _addProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, profile);
694        }
695        _save(node);
696    }
697    
698    /**
699     * Helper for {@link ModifiableACLAmetysObject#removeAllowedProfilesForAnonymous(Set)}
700     * @param node The JCR node for the Ametys object
701     * @param profileIds The profiles to remove
702     */
703    public static void removeAllowedProfilesForAnonymous(Node node, Set<String> profileIds)
704    {
705        Node aclNode = _getOrCreateACLNode(node);
706        for (String profile : profileIds)
707        {
708            _removeProperty(aclNode, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, profile);
709        }
710        _save(node);
711    }
712    
713    
714    /* ----------------------------- */
715    /* DENIED PROFILES FOR ANONYMOUS */
716    /* ----------------------------- */
717    
718    /**
719     * Helper for {@link ACLAmetysObject#getDeniedProfilesForAnonymous()}
720     * @param node The JCR node for the Ametys object
721     * @return the denied profiles an anonymous user has on the given node
722     */
723    public static Set<String> getDeniedProfilesForAnonymous(Node node)
724    {
725        Node aclNode = _getACLNode(node);
726        if (aclNode == null)
727        {
728            return Collections.EMPTY_SET;
729        }
730        else
731        {
732            return _getProperty(aclNode, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES);
733        }
734    }
735    
736    /**
737     * Helper for {@link ModifiableACLAmetysObject#addDeniedProfilesForAnyConnectedUser(Set)}
738     * @param node The JCR node for the Ametys object
739     * @param profileIds The profiles to add
740     */
741    public static void addDeniedProfilesForAnonymous(Node node, Set<String> profileIds)
742    {
743        Node aclNode = _getOrCreateACLNode(node);
744        for (String profile : profileIds)
745        {
746            _addProperty(aclNode, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, profile);
747        }
748        _save(node);
749    }
750    
751    /**
752     * Helper for {@link ModifiableACLAmetysObject#removeDeniedProfilesForAnyConnectedUser(Set)}
753     * @param node The JCR node for the Ametys object
754     * @param profileIds The profiles to remove
755     */
756    public static void removeDeniedProfilesForAnonymous(Node node, Set<String> profileIds)
757    {
758        Node aclNode = _getOrCreateACLNode(node);
759        for (String profile : profileIds)
760        {
761            _removeProperty(aclNode, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, profile);
762        }
763        _save(node);
764    }
765    
766    
767    /* --------------------------- */
768    /* MANAGEMENT OF ALLOWED USERS */
769    /* --------------------------- */
770    /**
771     * Helper for {@link ACLAmetysObject#getAllowedProfilesForUser(UserIdentity)}
772     * @param node The JCR node for the Ametys object
773     * @param user The user
774     * @return The denied profiles
775     */
776    public static Set<String> getAllowedProfilesForUser (Node node, UserIdentity user)
777    {
778        Node userNode = _getUserNode(node, user);
779        if (userNode == null)
780        {
781            return new HashSet<>();
782        }
783        
784        return _getProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES);
785    }
786    
787    /**
788     * Helper for {@link ACLAmetysObject#getAllowedProfilesForUsers()}
789     * @param node The JCR node for the Ametys object
790     * @return The map of allowed users (keys) with their assigned profiles (values)
791     */
792    public static Map<UserIdentity, Set<String>> getAllowedProfilesForUsers(Node node)
793    {
794        Map<UserIdentity, Set<String>> result = new HashMap<>();
795        
796        try
797        {
798            Node usersNode = _getUsersNode(node);
799            if (usersNode == null)
800            {
801                return result;
802            }
803            
804            NodeIterator populationsIterator = usersNode.getNodes();
805            while (populationsIterator.hasNext())
806            {
807                Node population = populationsIterator.nextNode();
808                NodeIterator usersIterator = population.getNodes();
809                while (usersIterator.hasNext())
810                {
811                    Node user = usersIterator.nextNode();
812                    Set<String> allowedProfiles = _getProperty(user, __PROPERTY_NAME_ALLOWED_PROFILES);
813                    if (!allowedProfiles.isEmpty())
814                    {
815                        result.put(new UserIdentity(user.getName(), population.getName()), allowedProfiles);
816                    }
817                }
818            }
819        }
820        catch (RepositoryException e)
821        {
822            throw new AmetysRepositoryException("Unable to get allowed users", e);
823        }
824        return result;
825    }
826
827    /**
828     * Helper for {@link ACLAmetysObject#getAllowedUsers(String)}
829     * @param node The JCR node for the Ametys object
830     * @param profileId The id of the profile
831     * @return The allowed users with that profile on that ametys object
832     */
833    public static Set<UserIdentity> getAllowedUsers(Node node, String profileId)
834    {
835        try
836        {
837            Set<UserIdentity> allowedUsers = new HashSet<>();
838            
839            Node usersNode = _getUsersNode(node);
840            if (usersNode == null)
841            {
842                return allowedUsers;
843            }
844            
845            NodeIterator popNodes = usersNode.getNodes();
846            
847            while (popNodes.hasNext())
848            {
849                Node popNode = (Node) popNodes.next();
850                
851                NodeIterator userNodes = popNode.getNodes();
852                
853                while (userNodes.hasNext())
854                {
855                    Node userNode = (Node) userNodes.next();
856                    Set<String> allowedProfiles = _getProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES);
857                    if (allowedProfiles.contains(profileId))
858                    {
859                        allowedUsers.add(new UserIdentity(userNode.getName(), popNode.getName()));
860                    }
861                }
862            }
863            
864            return allowedUsers;
865            
866            /*Expression expr = new AllowedProfileExpression(profileId);
867            AmetysObjectIterable<JCRAmetysObject> users = getACLUsers(node, expr);
868            
869            return users.stream()
870                    .map(LambdaUtils.wrap(aclUser -> new UserIdentity(aclUser.getName(), aclUser.getNode().getParent().getName())))
871                    .collect(Collectors.toSet());*/
872        }
873        catch (RepositoryException e)
874        {
875            throw new AmetysRepositoryException(e);
876        }
877    }
878
879    /**
880     * Helper for {@link ModifiableACLAmetysObject#addAllowedUsers(Set, String)}
881     * @param users The users to add
882     * @param node The JCR node for the Ametys object
883     * @param profileId The id of the profile
884     */
885    public static void addAllowedUsers(Set<UserIdentity> users, Node node, String profileId)
886    {
887        for (UserIdentity userIdentity : users)
888        {
889            Node userNode = _getOrCreateUserNode(node, userIdentity);
890            _addProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId);
891        }
892        _save(node);
893    }
894
895    /**
896     * Helper for {@link ModifiableACLAmetysObject#removeAllowedUsers(Set, String)}
897     * @param users The users to remove
898     * @param node The JCR node for the Ametys object
899     * @param profileId The id of the profile
900     */
901    public static void removeAllowedUsers(Set<UserIdentity> users, Node node, String profileId)
902    {
903        for (UserIdentity userIdentity : users)
904        {
905            Node userNode = _getOrCreateUserNode(node, userIdentity);
906            _removeProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId);
907        }
908        _save(node);
909    }
910
911    /**
912     * Helper for {@link ModifiableACLAmetysObject#removeAllowedGroups(Set)}
913     * @param users The users to remove
914     * @param node The JCR node for the Ametys object
915     */
916    public static void removeAllowedUsers(Set<UserIdentity> users, Node node)
917    {
918        for (UserIdentity userIdentity : users)
919        {
920            Node userNode = _getOrCreateUserNode(node, userIdentity);
921            _setProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES, Collections.EMPTY_SET);
922        } 
923        _save(node);
924    }
925    
926    
927    /* ---------------------------- */
928    /* MANAGEMENT OF ALLOWED GROUPS */
929    /* ---------------------------- */
930
931    /**
932     * Helper for {@link ACLAmetysObject#getAllowedProfilesForGroups()} 
933     * @param node The JCR node for the Ametys object
934     * @return The map of allowed groups (keys) with their assigned profiles (values)
935     */
936    public static Map<GroupIdentity, Set<String>> getAllowedProfilesForGroups(Node node)
937    {
938        Map<GroupIdentity, Set<String>> result = new HashMap<>();
939        
940        try
941        {
942            Node groupsNode = _getGroupsNode(node);
943            if (groupsNode == null)
944            {
945                return result;
946            }
947            
948            NodeIterator groupDirectoriesIterator = groupsNode.getNodes();
949            while (groupDirectoriesIterator.hasNext())
950            {
951                Node groupDirectory = groupDirectoriesIterator.nextNode();
952                NodeIterator groupsIterator = groupDirectory.getNodes();
953                while (groupsIterator.hasNext())
954                {
955                    Node group = groupsIterator.nextNode();
956                    Set<String> allowedProfiles = _getProperty(group, __PROPERTY_NAME_ALLOWED_PROFILES);
957                    if (!allowedProfiles.isEmpty())
958                    {
959                        result.put(new GroupIdentity(Text.unescapeIllegalJcrChars(group.getName()), groupDirectory.getName()), allowedProfiles);
960                    }
961                }
962            }
963        }
964        catch (RepositoryException e)
965        {
966            throw new AmetysRepositoryException("Unable to get allowed groups", e);
967        }
968        return result;
969    }
970
971    /**
972     * Helper for {@link ACLAmetysObject#getAllowedGroups(String)} 
973     * @param node The JCR node for the Ametys object
974     * @param profileId The id of the profile
975     * @return The allowed groups with that profile on that ametys object
976     */
977    public static Set<GroupIdentity> getAllowedGroups(Node node, String profileId)
978    {
979        try
980        {
981            Set<GroupIdentity> deniedGroups = new HashSet<>();
982            
983            Node groupsNode = _getGroupsNode(node);
984            if (groupsNode == null)
985            {
986                return deniedGroups;
987            }
988            
989            NodeIterator gpDirNodes = groupsNode.getNodes();
990            
991            while (gpDirNodes.hasNext())
992            {
993                Node gpDirNode = (Node) gpDirNodes.next();
994                
995                NodeIterator gpNodes = gpDirNode.getNodes();
996                
997                while (gpNodes.hasNext())
998                {
999                    Node gpNode = (Node) gpNodes.next();
1000                    Set<String> allowedProfiles = _getProperty(gpNode, __PROPERTY_NAME_ALLOWED_PROFILES);
1001                    if (allowedProfiles.contains(profileId))
1002                    {
1003                        deniedGroups.add(new GroupIdentity(Text.unescapeIllegalJcrChars(gpNode.getName()), gpDirNode.getName()));
1004                    }
1005                }
1006            }
1007            
1008            return deniedGroups;
1009            
1010            /*Expression expr = new AllowedProfileExpression(profileId);
1011            AmetysObjectIterable<JCRAmetysObject> groups = getACLGroups(node, expr);
1012            
1013            return groups.stream()
1014                    .map(LambdaUtils.wrap(aclGroup -> new GroupIdentity(Text.unescapeIllegalJcrChars(aclGroup.getName()), aclGroup.getNode().getParent().getName())))
1015                    .collect(Collectors.toSet());*/
1016        }
1017        catch (RepositoryException e)
1018        {
1019            throw new AmetysRepositoryException(e);
1020        }
1021    }
1022
1023    /**
1024     * Helper for {@link ModifiableACLAmetysObject#addAllowedGroups(Set, String)}
1025     * @param groups The groups to add
1026     * @param node The JCR node for the Ametys object
1027     * @param profileId The id of the profile
1028     */
1029    public static void addAllowedGroups(Set<GroupIdentity> groups, Node node, String profileId)
1030    {
1031        for (GroupIdentity groupIdentity : groups)
1032        {
1033            Node groupNode = _getOrCreateGroupNode(node, groupIdentity);
1034            _addProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId);
1035        }
1036        _save(node);
1037    }
1038
1039    /**
1040     * Helper for {@link ModifiableACLAmetysObject#removeAllowedGroups(Set, String)}
1041     * @param groups The groups to remove
1042     * @param node The JCR node for the Ametys object
1043     * @param profileId The id of the profile
1044     */
1045    public static void removeAllowedGroups(Set<GroupIdentity> groups, Node node, String profileId)
1046    {
1047        for (GroupIdentity groupIdentity : groups)
1048        {
1049            Node groupNode = _getOrCreateGroupNode(node, groupIdentity);
1050            _removeProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId);
1051        }
1052        _save(node);
1053    }
1054
1055    /**
1056     * Helper for {@link ModifiableACLAmetysObject#removeAllowedGroups(Set)}
1057     * @param groups The groups to remove
1058     * @param node The JCR node for the Ametys object
1059     */
1060    public static void removeAllowedGroups(Set<GroupIdentity> groups, Node node)
1061    {
1062        for (GroupIdentity groupIdentity : groups)
1063        {
1064            Node groupNode = _getOrCreateGroupNode(node, groupIdentity);
1065            _setProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES, Collections.EMPTY_SET);
1066        } 
1067        _save(node);
1068    }
1069
1070    
1071    /* ---------------------------- */
1072    /* MANAGEMENT OF DENIED USERS */
1073    /* ---------------------------- */
1074    /**
1075     * Helper for {@link ACLAmetysObject#getDeniedProfilesForUser(UserIdentity)}
1076     * @param node The JCR node for the Ametys object
1077     * @param user The user
1078     * @return The denied profiles
1079     */
1080    public static Set<String> getDeniedProfilesForUser (Node node, UserIdentity user)
1081    {
1082        Node userNode = _getUserNode(node, user);
1083        if (userNode == null)
1084        {
1085            return new HashSet<>();
1086        }
1087        
1088        return _getProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES);
1089    }
1090    
1091    /**
1092     * Helper for {@link ACLAmetysObject#getDeniedProfilesForUsers()}
1093     * @param node The JCR node for the Ametys object
1094     * @return The map of denied users (keys) with their assigned profiles (values)
1095     */
1096    public static Map<UserIdentity, Set<String>> getDeniedProfilesForUsers(Node node)
1097    {
1098        Map<UserIdentity, Set<String>> result = new HashMap<>();
1099        
1100        try
1101        {
1102            Node usersNode = _getUsersNode(node);
1103            if (usersNode == null)
1104            {
1105                return result;
1106            }
1107            
1108            NodeIterator populationsIterator = usersNode.getNodes();
1109            while (populationsIterator.hasNext())
1110            {
1111                Node population = populationsIterator.nextNode();
1112                NodeIterator usersIterator = population.getNodes();
1113                while (usersIterator.hasNext())
1114                {
1115                    Node user = usersIterator.nextNode();
1116                    Set<String> allowedProfiles = _getProperty(user, __PROPERTY_NAME_DENIED_PROFILES);
1117                    if (!allowedProfiles.isEmpty())
1118                    {
1119                        result.put(new UserIdentity(user.getName(), population.getName()), allowedProfiles);
1120                    }
1121                }
1122            }
1123        }
1124        catch (RepositoryException e)
1125        {
1126            throw new AmetysRepositoryException("Unable to get denied users", e);
1127        }
1128        return result;
1129    }
1130
1131    /**
1132     * Helper for {@link ACLAmetysObject#getDeniedUsers(String)}
1133     * @param node The JCR node for the Ametys object
1134     * @param profileId The id of the profile
1135     * @return The denied users with that profile on that ametys object
1136     */
1137    public static Set<UserIdentity> getDeniedUsers(Node node, String profileId)
1138    {
1139        
1140        try
1141        {
1142            Set<UserIdentity> deniedUsers = new HashSet<>();
1143            
1144            Node usersNode = _getUsersNode(node);
1145            if (usersNode == null)
1146            {
1147                return deniedUsers;
1148            }
1149            
1150            NodeIterator popNodes = usersNode.getNodes();
1151            while (popNodes.hasNext())
1152            {
1153                Node popNode = (Node) popNodes.next();
1154                
1155                NodeIterator userNodes = popNode.getNodes();
1156                
1157                while (userNodes.hasNext())
1158                {
1159                    Node userNode = (Node) userNodes.next();
1160                    Set<String> allowedProfiles = _getProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES);
1161                    if (allowedProfiles.contains(profileId))
1162                    {
1163                        deniedUsers.add(new UserIdentity(userNode.getName(), popNode.getName()));
1164                    }
1165                }
1166            }
1167            
1168            return deniedUsers;
1169            
1170            /*Expression expr = new DeniedProfileExpression(profileId);
1171            AmetysObjectIterable<JCRAmetysObject> users = getACLUsers(node, expr);
1172            
1173            return users.stream()
1174                    .map(LambdaUtils.wrap(aclUser -> new UserIdentity(aclUser.getName(), aclUser.getNode().getParent().getName())))
1175                    .collect(Collectors.toSet());*/
1176        }
1177        catch (RepositoryException e)
1178        {
1179            throw new AmetysRepositoryException(e);
1180        }
1181    }
1182
1183    /**
1184     * Helper for {@link ModifiableACLAmetysObject#addDeniedUsers(Set, String)}
1185     * @param users The users to add
1186     * @param node The JCR node for the Ametys object
1187     * @param profileId The id of the profile
1188     */
1189    public static void addDeniedUsers(Set<UserIdentity> users, Node node, String profileId)
1190    {
1191        for (UserIdentity userIdentity : users)
1192        {
1193            Node userNode = _getOrCreateUserNode(node, userIdentity);
1194            _addProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES, profileId);
1195        }
1196        _save(node);
1197    }
1198
1199    /**
1200     * Helper for {@link ModifiableACLAmetysObject#removeDeniedUsers(Set, String)}
1201     * @param users The users to remove
1202     * @param node The JCR node for the Ametys object
1203     * @param profileId The id of the profile
1204     */
1205    public static void removeDeniedUsers(Set<UserIdentity> users, Node node, String profileId)
1206    {
1207        for (UserIdentity userIdentity : users)
1208        {
1209            Node userNode = _getOrCreateUserNode(node, userIdentity);
1210            _removeProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES, profileId);
1211        }
1212        _save(node);
1213    }
1214
1215    /**
1216     * Helper for {@link ModifiableACLAmetysObject#removeDeniedUsers(Set)}
1217     * @param users The users to remove
1218     * @param node The JCR node for the Ametys object
1219     */
1220    public static void removeDeniedUsers(Set<UserIdentity> users, Node node)
1221    {
1222        for (UserIdentity userIdentity : users)
1223        {
1224            Node userNode = _getOrCreateUserNode(node, userIdentity);
1225            _setProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES, Collections.EMPTY_SET);
1226        } 
1227        _save(node);
1228    }
1229
1230    
1231    /* ----------------------------- */
1232    /* MANAGEMENT OF DENIED GROUPS */
1233    /* ----------------------------- */
1234
1235    /**
1236     * Helper for {@link ACLAmetysObject#getDeniedProfilesForGroups()}
1237     * @param node The JCR node for the Ametys object
1238     * @return The map of denied groups (keys) with their assigned profiles (values)
1239     */
1240    public static Map<GroupIdentity, Set<String>> getDeniedProfilesForGroups(Node node)
1241    {
1242        Map<GroupIdentity, Set<String>> result = new HashMap<>();
1243        
1244        try
1245        {
1246            Node groupsNode = _getGroupsNode(node);
1247            if (groupsNode == null)
1248            {
1249                return result;
1250            }
1251            
1252            NodeIterator groupDirectoriesIterator = groupsNode.getNodes();
1253            while (groupDirectoriesIterator.hasNext())
1254            {
1255                Node groupDirectory = groupDirectoriesIterator.nextNode();
1256                NodeIterator groupsIterator = groupDirectory.getNodes();
1257                while (groupsIterator.hasNext())
1258                {
1259                    Node group = groupsIterator.nextNode();
1260                    Set<String> allowedProfiles = _getProperty(group, __PROPERTY_NAME_DENIED_PROFILES);
1261                    if (!allowedProfiles.isEmpty())
1262                    {
1263                        result.put(new GroupIdentity(Text.unescapeIllegalJcrChars(group.getName()), groupDirectory.getName()), allowedProfiles);
1264                    }
1265                }
1266            }
1267        }
1268        catch (RepositoryException e)
1269        {
1270            throw new AmetysRepositoryException("Unable to get allowed groups", e);
1271        }
1272        return result;
1273    }
1274
1275    /**
1276     * Helper for {@link ACLAmetysObject#getDeniedGroups(String)}
1277     * @param node The JCR node for the Ametys object
1278     * @param profileId The id of the profile
1279     * @return The denied groups with that profile on that ametys object
1280     */
1281    public static Set<GroupIdentity> getDeniedGroups(Node node, String profileId)
1282    {
1283        try
1284        {
1285            Set<GroupIdentity> deniedGroups = new HashSet<>();
1286            
1287            Node groupsNode = _getGroupsNode(node);
1288            if (groupsNode == null)
1289            {
1290                return deniedGroups;
1291            }
1292            
1293            NodeIterator gpDirNodes = groupsNode.getNodes();
1294            
1295            while (gpDirNodes.hasNext())
1296            {
1297                Node gpDirNode = (Node) gpDirNodes.next();
1298                
1299                NodeIterator gpNodes = gpDirNode.getNodes();
1300                
1301                while (gpNodes.hasNext())
1302                {
1303                    Node gpNode = (Node) gpNodes.next();
1304                    Set<String> allowedProfiles = _getProperty(gpNode, __PROPERTY_NAME_DENIED_PROFILES);
1305                    if (allowedProfiles.contains(profileId))
1306                    {
1307                        deniedGroups.add(new GroupIdentity(Text.unescapeIllegalJcrChars(gpNode.getName()), gpDirNode.getName()));
1308                    }
1309                }
1310            }
1311            
1312            return deniedGroups;
1313            
1314            /*Expression expr = new DeniedProfileExpression(profileId);
1315            AmetysObjectIterable<JCRAmetysObject> groups = getACLGroups(node, expr);
1316            
1317            return groups.stream()
1318                    .map(LambdaUtils.wrap(aclGroup -> new GroupIdentity(Text.unescapeIllegalJcrChars(aclGroup.getName()), aclGroup.getNode().getParent().getName())))
1319                    .collect(Collectors.toSet());*/
1320        }
1321        catch (RepositoryException e)
1322        {
1323            throw new AmetysRepositoryException(e);
1324        }
1325    }
1326
1327    /**
1328     * Helper for {@link ModifiableACLAmetysObject#addDeniedGroups(Set, String)}
1329     * @param groups The groups to add
1330     * @param node The JCR node for the Ametys object
1331     * @param profileId The id of the profile
1332     */
1333    public static void addDeniedGroups(Set<GroupIdentity> groups, Node node, String profileId)
1334    {
1335        for (GroupIdentity groupIdentity : groups)
1336        {
1337            Node groupNode = _getOrCreateGroupNode(node, groupIdentity);
1338            _addProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES, profileId);
1339        }
1340        _save(node);
1341    }
1342
1343    /**
1344     * Helper for {@link ModifiableACLAmetysObject#removeDeniedGroups(Set, String)}
1345     * @param groups The groups to remove
1346     * @param node The JCR node for the Ametys object
1347     * @param profileId The id of the profile
1348     */
1349    public static void removeDeniedGroups(Set<GroupIdentity> groups, Node node, String profileId)
1350    {
1351        for (GroupIdentity groupIdentity : groups)
1352        {
1353            Node groupNode = _getOrCreateGroupNode(node, groupIdentity);
1354            _removeProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES, profileId);
1355        }
1356        _save(node);
1357    }
1358
1359    /**
1360     * Helper for {@link ModifiableACLAmetysObject#removeDeniedGroups(Set)}
1361     * @param groups The groups to remove
1362     * @param node The JCR node for the Ametys object
1363     */
1364    public static void removeDeniedGroups(Set<GroupIdentity> groups, Node node)
1365    {
1366        for (GroupIdentity groupIdentity : groups)
1367        {
1368            Node groupNode = _getOrCreateGroupNode(node, groupIdentity);
1369            _setProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES, Collections.EMPTY_SET);
1370        } 
1371        _save(node);
1372    }
1373    
1374    
1375    /* ------ */
1376    /* REMOVE */
1377    /* ------ */
1378    
1379    /**
1380     * Helper for {@link ModifiableACLAmetysObjectProfileAssignmentStorage#removeProfile(String)}
1381     * @param profileId The id of the profile
1382     */
1383    public static void removeProfile(String profileId)
1384    {
1385        // Remove this profile set as allowed or denied in users 
1386        Expression expr = new OrExpression(new AllowedProfileExpression(profileId), new DeniedProfileExpression(profileId));
1387        NodeIterator users = getACLUsers(expr);
1388        while (users.hasNext())
1389        {
1390            Node userNode = (Node) users.next();
1391            _removeProperty(userNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId);
1392            _removeProperty(userNode, __PROPERTY_NAME_DENIED_PROFILES, profileId);
1393            _save(userNode);
1394        }
1395        
1396        // Remove this profile set as allowed or denied in groups 
1397        NodeIterator groups = getACLGroups(expr);
1398        while (groups.hasNext())
1399        {
1400            Node groupNode = (Node) groups.next();
1401            _removeProperty(groupNode, __PROPERTY_NAME_ALLOWED_PROFILES, profileId);
1402            _removeProperty(groupNode, __PROPERTY_NAME_DENIED_PROFILES, profileId);
1403            _save(groupNode);
1404        }
1405        
1406        // Remove this profile set as allowed or denied for anonymous and any connected 
1407        expr = new OrExpression(new AnonymousAllowedProfileExpression(profileId), new AnonymousDeniedProfileExpression(profileId), new AnyConnectedAllowedProfileExpression(profileId), new AnyConnectedDeniedProfileExpression(profileId));
1408        NodeIterator nodes = getACLRoots(null, expr);
1409        while (nodes.hasNext())
1410        {
1411            Node node = (Node) nodes.next();
1412            _removeProperty(node, __PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, profileId);
1413            _removeProperty(node, __PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES, profileId);
1414            _removeProperty(node, __PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, profileId);
1415            _removeProperty(node, __PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, profileId);
1416            _save(node);
1417        }
1418    }
1419    
1420    /**
1421     * Helper for {@link ModifiableACLAmetysObjectProfileAssignmentStorage#removeUser(UserIdentity)}
1422     * @param user The user
1423     */
1424    public static void removeUser(UserIdentity user)
1425    {
1426        NodeIterator users = getACLUsers(user, null);
1427        
1428        while (users.hasNext())
1429        {
1430            Node userNode = (Node) users.next();
1431            try
1432            {
1433                userNode.remove();
1434                _save(userNode);
1435            }
1436            catch (RepositoryException e)
1437            {
1438                throw new AmetysRepositoryException(e);
1439            }
1440        }
1441    }
1442    
1443    /**
1444     * Helper for {@link ModifiableACLAmetysObjectProfileAssignmentStorage#removeGroup(GroupIdentity)}
1445     * @param group The group
1446     */
1447    public static void removeGroup(GroupIdentity group)
1448    {
1449        NodeIterator groups = getACLGroups(group, null);
1450        while (groups.hasNext())
1451        {
1452            Node gpNode = (Node) groups.next();
1453            try
1454            {
1455                gpNode.remove();
1456                _save(gpNode);
1457            }
1458            catch (RepositoryException e)
1459            {
1460                throw new AmetysRepositoryException(e);
1461            }
1462        }
1463    }
1464    
1465    /* --------------- */
1466    /* INHERITANCE     */
1467    /* --------------- */
1468    /**
1469     * Helper for {@link ACLAmetysObject#isInheritanceDisallowed()}
1470     * @param node The JCR node for the Ametys object
1471     * @return true if the inheritance is disallow of the given node
1472     */
1473    public static boolean isInheritanceDisallowed(Node node)
1474    {
1475        try
1476        {
1477            Node aclNode = _getACLNode(node);
1478            if (aclNode != null && aclNode.hasProperty(__PROPERTY_NAME_DISALLOW_INHERITANCE))
1479            {
1480                return aclNode.getProperty(__PROPERTY_NAME_DISALLOW_INHERITANCE).getBoolean();
1481            }
1482            return false;
1483        }
1484        catch (RepositoryException e)
1485        {
1486            throw new AmetysRepositoryException("Unable to get " + __PROPERTY_NAME_DISALLOW_INHERITANCE + " property", e);
1487        }
1488    }
1489    
1490    /**
1491     * Helper for {@link ModifiableACLAmetysObject#disallowInheritance(boolean)}
1492     * @param node The JCR node for the Ametys object
1493     * @param disallow true to disallow the inheritance, false otherwise
1494     */
1495    public static void disallowInheritance(Node node, boolean disallow)
1496    {
1497        Node aclNode = _getOrCreateACLNode(node);
1498        try
1499        {
1500            aclNode.setProperty(__PROPERTY_NAME_DISALLOW_INHERITANCE, disallow);
1501        }
1502        catch (RepositoryException e)
1503        {
1504            throw new AmetysRepositoryException("Unable to set " + __PROPERTY_NAME_DISALLOW_INHERITANCE + " property", e);
1505        }
1506        _save(node);
1507    }
1508    
1509    
1510    /* --------------- */
1511    /* PRIVATE METHODS */
1512    /* --------------- */
1513    
1514    private static void _checkLock(Node node) throws AmetysRepositoryException
1515    {
1516        try
1517        {
1518            if (node.isLocked())
1519            {
1520                LockManager lockManager = node.getSession().getWorkspace().getLockManager();
1521                
1522                Lock lock = lockManager.getLock(node.getPath());
1523                Node lockHolder = lock.getNode();
1524                
1525                lockManager.addLockToken(lockHolder.getProperty(RepositoryConstants.METADATA_LOCKTOKEN).getString());
1526            }
1527        }
1528        catch (RepositoryException e)
1529        {
1530            throw new AmetysRepositoryException("Unable to add lock token on ACL node", e);
1531        }
1532    }
1533    
1534    private static Node _getOrCreateACLNode(Node node)
1535    {
1536        try
1537        {
1538            if (node.hasNode(__NODE_NAME_ROOT_ACL))
1539            {
1540                return node.getNode(__NODE_NAME_ROOT_ACL);
1541            }
1542            else
1543            {
1544                _checkLock(node);
1545                return node.addNode(__NODE_NAME_ROOT_ACL, __NODETYPE_ROOT_ACL);
1546            }
1547        }
1548        catch (RepositoryException e)
1549        {
1550            throw new AmetysRepositoryException("Error while getting root ACL node.", e);
1551        }
1552    }
1553    
1554    private static Node _getACLNode(Node node)
1555    {
1556        try
1557        {
1558            if (node.hasNode(__NODE_NAME_ROOT_ACL))
1559            {
1560                return node.getNode(__NODE_NAME_ROOT_ACL);
1561            }
1562            else
1563            {
1564                return null;
1565            }
1566        }
1567        catch (RepositoryException e)
1568        {
1569            throw new AmetysRepositoryException("Error while getting root ACL node.", e);
1570        }
1571    }
1572    
1573    private static Node _getOrCreateUsersNode(Node node)
1574    {
1575        try
1576        {
1577            Node aclNode = _getOrCreateACLNode(node);
1578            if (aclNode.hasNode(__NODE_NAME_ACL_USERS))
1579            {
1580                return aclNode.getNode(__NODE_NAME_ACL_USERS);
1581            }
1582            else
1583            {
1584                return aclNode.addNode(__NODE_NAME_ACL_USERS, __NODETYPE_UNSTRUCTURED);
1585            }
1586        }
1587        catch (RepositoryException e)
1588        {
1589            throw new AmetysRepositoryException("Error while getting 'users' ACL node.", e);
1590        }
1591    }
1592    
1593    private static Node _getUserNode(Node node, UserIdentity user)
1594    {
1595        try
1596        {
1597            Node aclNode = _getACLNode(node);
1598            if (aclNode != null && aclNode.hasNode(__NODE_NAME_ACL_USERS))
1599            {
1600                Node aclUsersNode = aclNode.getNode(__NODE_NAME_ACL_USERS);
1601                if (aclUsersNode.hasNode(user.getPopulationId()))
1602                {
1603                    Node popNode = aclUsersNode.getNode(user.getPopulationId());
1604                    if (popNode.hasNode(user.getLogin()))
1605                    {
1606                        return popNode.getNode(user.getLogin());
1607                    }
1608                }
1609            }
1610            
1611            return null;
1612        }
1613        catch (RepositoryException e)
1614        {
1615            throw new AmetysRepositoryException("Error while getting 'users' ACL node.", e);
1616        }
1617    }
1618    
1619    private static Node _getUsersNode(Node node)
1620    {
1621        try
1622        {
1623            Node aclNode = _getACLNode(node);
1624            if (aclNode != null && aclNode.hasNode(__NODE_NAME_ACL_USERS))
1625            {
1626                return aclNode.getNode(__NODE_NAME_ACL_USERS);
1627            }
1628            else
1629            {
1630                return null;
1631            }
1632        }
1633        catch (RepositoryException e)
1634        {
1635            throw new AmetysRepositoryException("Error while getting 'users' ACL node.", e);
1636        }
1637    }
1638    
1639    private static Node _getOrCreateGroupsNode(Node node)
1640    {
1641        try
1642        {
1643            Node aclNode = _getOrCreateACLNode(node);
1644            if (aclNode.hasNode(__NODE_NAME_ACL_GROUPS))
1645            {
1646                return aclNode.getNode(__NODE_NAME_ACL_GROUPS);
1647            }
1648            else
1649            {
1650                return aclNode.addNode(__NODE_NAME_ACL_GROUPS, __NODETYPE_UNSTRUCTURED);
1651            }
1652        }
1653        catch (RepositoryException e)
1654        {
1655            throw new AmetysRepositoryException("Error while getting 'groups' ACL node.", e);
1656        }
1657    }
1658    
1659    private static Node _getGroupsNode(Node node)
1660    {
1661        try
1662        {
1663            Node aclNode = _getACLNode(node);
1664            if (aclNode != null && aclNode.hasNode(__NODE_NAME_ACL_GROUPS))
1665            {
1666                return aclNode.getNode(__NODE_NAME_ACL_GROUPS);
1667            }
1668            else
1669            {
1670                return null;
1671            }
1672        }
1673        catch (RepositoryException e)
1674        {
1675            throw new AmetysRepositoryException("Error while getting 'groups' ACL node.", e);
1676        }
1677    }
1678    
1679    private static Node _getOrCreateUserNode(Node node, UserIdentity userIdentity)
1680    {
1681        try
1682        {
1683            Node usersNode = _getOrCreateUsersNode(node);
1684            String population = userIdentity.getPopulationId();
1685            String login = userIdentity.getLogin();
1686            
1687            if (usersNode.hasNode(population))
1688            {
1689                Node populationNode = usersNode.getNode(population);
1690                if (populationNode.hasNode(login))
1691                {
1692                    return populationNode.getNode(login);
1693                }
1694                else
1695                {
1696                    return populationNode.addNode(login, __NODETYPE_ACL_USER);
1697                }
1698            }
1699            else
1700            {
1701                return usersNode.addNode(population, __NODETYPE_UNSTRUCTURED).addNode(login, __NODETYPE_ACL_USER);
1702            }
1703        }
1704        catch (RepositoryException e)
1705        {
1706            throw new AmetysRepositoryException(String.format("Error while getting 'user' ACL node for %s.", userIdentity.toString()), e);
1707        }
1708    }
1709    
1710    private static Node _getOrCreateGroupNode(Node node, GroupIdentity groupIdentity)
1711    {
1712        try
1713        {
1714            Node groupsNode = _getOrCreateGroupsNode(node);
1715            String directoryId = groupIdentity.getDirectoryId();
1716            String id = Text.escapeIllegalJcrChars(groupIdentity.getId());
1717            
1718            if (groupsNode.hasNode(directoryId))
1719            {
1720                Node populationNode = groupsNode.getNode(directoryId);
1721                if (populationNode.hasNode(id))
1722                {
1723                    return populationNode.getNode(id);
1724                }
1725                else
1726                {
1727                    return populationNode.addNode(id, __NODETYPE_ACL_GROUP);
1728                }
1729            }
1730            else
1731            {
1732                return groupsNode.addNode(directoryId, __NODETYPE_UNSTRUCTURED).addNode(Text.escapeIllegalJcrChars(id), __NODETYPE_ACL_GROUP);
1733            }
1734        }
1735        catch (RepositoryException e)
1736        {
1737            throw new AmetysRepositoryException(String.format("Error while getting 'group' ACL node for %s.", groupIdentity.toString()), e);
1738        }
1739    }
1740    
1741    private static Set<String> _getProperty(Node node, String propertyName)
1742    {
1743        try
1744        {
1745            Value[] values = node.getProperty(propertyName).getValues();
1746            Set<String> result = new HashSet<>();
1747            for (Value value : values)
1748            {
1749                result.add(value.getString());
1750            }
1751            return result;
1752        }
1753        catch (PathNotFoundException e)
1754        {
1755            return new HashSet<>();
1756        }
1757        catch (RepositoryException e)
1758        {
1759            throw new AmetysRepositoryException("Unable to get " + propertyName + " property", e);
1760        }
1761    }
1762    
1763    private static void _setProperty(Node node, String propertyName, Set<String> profiles)
1764    {
1765        try
1766        {
1767            node.setProperty(propertyName, profiles.toArray(new String[profiles.size()]));
1768        }
1769        catch (RepositoryException e)
1770        {
1771            throw new AmetysRepositoryException("Unable to set " + propertyName + " property", e);
1772        }
1773    }
1774    
1775    private static void _addProperty(Node node, String propertyName, String profileToAdd)
1776    {
1777        Set<String> profiles = _getProperty(node, propertyName);
1778        if (!profiles.contains(profileToAdd))
1779        {
1780            profiles.add(profileToAdd);
1781            _setProperty(node, propertyName, profiles);
1782        }
1783    }
1784    
1785    private static void _removeProperty(Node node, String propertyName, String profileToRemove)
1786    {
1787        Set<String> profiles = _getProperty(node, propertyName);
1788        if (profiles.contains(profileToRemove))
1789        {
1790            profiles.remove(profileToRemove);
1791            _setProperty(node, propertyName, profiles);
1792        }
1793    }
1794    
1795    private static void _save(Node node)
1796    {
1797        try
1798        {
1799            node.getSession().save();
1800        }
1801        catch (RepositoryException e)
1802        {
1803            throw new AmetysRepositoryException("Unable to save changes", e);
1804        }
1805    }
1806    
1807    /* ---------------------------------------*/
1808    /*      JCR EXPRESSIONS FOR PROFILES      */
1809    /* ---------------------------------------*/
1810    
1811    static class AllowedProfileExpression extends ACLProfileExpression
1812    {
1813        public AllowedProfileExpression (String ... profileIds)
1814        {
1815            super(__PROPERTY_NAME_ALLOWED_PROFILES, profileIds);
1816        }
1817    }
1818    
1819    static class DeniedProfileExpression extends ACLProfileExpression
1820    {
1821        public DeniedProfileExpression (String ... profileIds)
1822        {
1823            super(__PROPERTY_NAME_DENIED_PROFILES, profileIds);
1824        }
1825    }
1826    
1827    static class AnyConnectedDeniedProfileExpression extends ACLProfileExpression
1828    {
1829        public AnyConnectedDeniedProfileExpression (String ... profileIds)
1830        {
1831            super(__PROPERTY_NAME_DENIED_ANY_CONNECTED_PROFILES, profileIds);
1832        }
1833    }
1834    
1835    static class AnyConnectedAllowedProfileExpression extends ACLProfileExpression
1836    {
1837        public AnyConnectedAllowedProfileExpression (String ... profileIds)
1838        {
1839            super(__PROPERTY_NAME_ALLOWED_ANY_CONNECTED_PROFILES, profileIds);
1840        }
1841    }
1842    
1843    static class AnonymousDeniedProfileExpression extends ACLProfileExpression
1844    {
1845        public AnonymousDeniedProfileExpression (String ... profileIds)
1846        {
1847            super(__PROPERTY_NAME_DENIED_ANONYMOUS_PROFILES, profileIds);
1848        }
1849    }
1850    
1851    static class AnonymousAllowedProfileExpression extends ACLProfileExpression
1852    {
1853        public AnonymousAllowedProfileExpression (String ... profileIds)
1854        {
1855            super(__PROPERTY_NAME_ALLOWED_ANONYMOUS_PROFILES, profileIds);
1856        }
1857    }
1858    
1859    static class ACLProfileExpression implements Expression
1860    {
1861        private String[] _profileIds;
1862        private String _propertyName;
1863        
1864        public ACLProfileExpression (String propertyName, String ... profileIds)
1865        {
1866            _propertyName = propertyName;
1867            _profileIds = profileIds;
1868        }
1869        
1870        @Override
1871        public String build()
1872        {
1873            boolean isFirst = true;
1874            StringBuilder sb = new StringBuilder("(");
1875            
1876            for (String profileId : _profileIds)
1877            {
1878                if (isFirst)
1879                {
1880                    isFirst = false;
1881                }
1882                else
1883                {
1884                    sb.append(" or ");
1885                }
1886                
1887                sb.append("@")
1888                    .append(_propertyName)
1889                    .append(Operator.EQ)
1890                    .append("'").append(profileId).append("'");
1891            }
1892            
1893            if (isFirst)
1894            {
1895                return "";
1896            }
1897            else
1898            {
1899                return sb.append(")").toString();
1900            }
1901        }
1902    }
1903}