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