001/*
002 *  Copyright 2015 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.web.pageaccess;
017
018import java.util.Collection;
019import java.util.Set;
020
021import org.apache.avalon.framework.component.Component;
022import org.apache.avalon.framework.logger.AbstractLogEnabled;
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.avalon.framework.service.Serviceable;
026
027import org.ametys.cms.repository.Content;
028import org.ametys.core.group.GroupIdentity;
029import org.ametys.core.right.AllowedUsers;
030import org.ametys.core.right.ProfileAssignmentStorageExtensionPoint;
031import org.ametys.core.right.RightManager;
032import org.ametys.core.user.CurrentUserProvider;
033import org.ametys.core.user.UserIdentity;
034import org.ametys.web.repository.content.WebContent;
035import org.ametys.web.repository.page.Page;
036
037/**
038 * Class managing access to contents on the front-office side.
039 */
040public class ContentAccessManager extends AbstractLogEnabled implements Component, Serviceable
041{
042    
043    /**
044     * Enumeration representing a content access status.
045     */
046    public enum ContentAccess 
047    {
048        /**
049         * The content can be viewed by all users because of one of these reasons:
050         * <ul>
051         *   <li>it appears on a public page</li>
052         *   <li>it doesn't appear on any page (orphan content) but its sitemap is freely accessible</li>
053         * </ul>
054         */
055        UNRESTRICTED,
056        
057        /**
058         * The content is available for anyconnected users
059         */
060        ANYCONNECTED_ALLOWED,
061        
062        /**
063         * The content appears on pages with limited access, and the given user can access at least one.
064         */
065        ALLOWED,
066        
067        /**
068         * The content appears on pages with limited access, but the given user can't access any.
069         */
070        FORBIDDEN
071    }
072    
073    /** The component role. */
074    public static final String ROLE = ContentAccessManager.class.getName();
075    
076    /** The right manager */
077    protected RightManager _rightManager;
078    
079    /** The current user provider */
080    protected CurrentUserProvider _currentUserProvider;
081    
082    /** The component handling profile storage */
083    protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
084    
085    @Override
086    public void service(ServiceManager manager) throws ServiceException
087    {
088        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
089        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
090        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) manager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
091    }
092    
093    /**
094     * Get the access status of a content, from the user currently connected to the front-office.
095     * @param content the content to test.
096     * @return the content access status.
097     */
098    public ContentAccess getAccess(Content content)
099    {
100        return getAccess(content, true);
101    }
102    
103    /**
104     * Get the access status of a content, from the user currently connected to the front-office.
105     * @param content the content to test.
106     * @param checkUser When true, the user's ability to view the page will be checked
107     * ({@link ContentAccess#ALLOWED} may be returned).<br>
108     * When false, only the content general availability will be checked: {@link ContentAccess#ALLOWED} will never be returned.
109     * @return the content access status.
110     */
111    public ContentAccess getAccess(Content content, boolean checkUser)
112    {
113        return getAccess(content, _currentUserProvider.getUser(), checkUser);
114    }
115    
116    /**
117     * Get the access status of a content from a given user.
118     * @param content the content to test.
119     * @param userIdentity the user identity, can be null (to test anonymous access).
120     * @return the content access status.
121     */
122    public ContentAccess getAccess(Content content, UserIdentity userIdentity)
123    {
124        return getAccess(content, userIdentity, true);
125    }
126    
127    /**
128     * Get the access status of a content from a given user.
129     * The user access is check ONLY on the content's pages. Has read access on content is not enough ! 
130     * @param content the content to test.
131     * @param userIdentity the user identity, can be null (to test anonymous access).
132     * @param checkUser When true, the user's ability to view the page will be checked
133     * ({@link ContentAccess#ALLOWED} may be returned).<br>
134     * When false, only the content general availability will be checked: {@link ContentAccess#ALLOWED} will never be returned.
135     * @return the content access status.
136     */
137    public ContentAccess getAccess(Content content, UserIdentity userIdentity, boolean checkUser)
138    {
139        if (!(content instanceof WebContent))
140        {
141            return _getContentAccess(content, userIdentity, checkUser);
142        }
143        WebContent webContent = (WebContent) content;
144        Collection<Page> refPages = webContent.getReferencingPages();
145        if (refPages.isEmpty())
146        {
147            return _getContentAccess(content, userIdentity, checkUser);
148        }
149        return _getAccessForContentPages(refPages, userIdentity, checkUser);
150    }
151
152    private ContentAccess _getAccessForContentPages(Collection<Page> contentPages, UserIdentity userIdentity, boolean checkUser)
153    {
154        for (Page page : contentPages)
155        {
156            // If the content is present in a page that is not restricted, it's visible by all.
157            if (_rightManager.hasAnonymousReadAccess(page))
158            {
159                return ContentAccess.UNRESTRICTED;
160            }
161            if (_rightManager.hasAnyConnectedUserReadAccess(page))
162            {
163                return ContentAccess.ANYCONNECTED_ALLOWED;
164            }
165                
166            // If the user is allowed to see at least one page containing the content, he can access the content.
167            if (checkUser && _rightManager.hasReadAccess(userIdentity, page))
168            {
169                return ContentAccess.ALLOWED;
170            }
171        }
172        return ContentAccess.FORBIDDEN;
173    }
174
175    private ContentAccess _getContentAccess(Content content, UserIdentity userIdentity, boolean checkUser)
176    {
177        // Returns content access regardless of its pages.
178        if (_rightManager.hasAnonymousReadAccess(content))
179        {
180            return ContentAccess.UNRESTRICTED;
181        }
182        
183        if (_rightManager.hasAnyConnectedUserReadAccess(content))
184        {
185            return ContentAccess.ANYCONNECTED_ALLOWED;
186        }
187        
188        return checkUser && _rightManager.hasReadAccess(userIdentity, content) ? ContentAccess.ALLOWED : ContentAccess.FORBIDDEN;
189    }
190    
191    /**
192     * Test if the content is displayed in a page having the same access rights as the current page.
193     * @param content the web content.
194     * @param currentPage the current page.
195     * @return true if the content is accessible, false otherwise.
196     */
197    public boolean isAccessibleByPage(WebContent content, Page currentPage)
198    {
199        if (!_rightManager.hasAnonymousReadAccess(currentPage))
200        {
201            return _isAccessibleByRestrictedPage(content, currentPage);
202        }
203        else
204        {
205            // The current page is not restricted: display only unrestricted contents
206            // N.B: the current user's right are *NOT* tested, as in this mode, the results are cached.
207            return getAccess(content, null, false) == ContentAccess.UNRESTRICTED;
208        }
209    }
210
211    private boolean _isAccessibleByRestrictedPage(WebContent content, Page currentPage)
212    {
213        AllowedUsers currentUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(currentPage);
214        boolean currentAllowAnyConnectedUser = currentUsersWithReadAccess.isAnyConnectedUserAllowed();
215        Set<UserIdentity> currentAllowedUsers = currentUsersWithReadAccess.getAllowedUsers();
216        Set<GroupIdentity> currentAllowedGroups = currentUsersWithReadAccess.getAllowedGroups();
217        Set<UserIdentity> currentDeniedUsers = currentUsersWithReadAccess.getDeniedUsers();
218        Set<GroupIdentity> currentDeniedGroups = currentUsersWithReadAccess.getDeniedGroups();
219        Collection<Page> refPages = content.getReferencingPages();
220        if (!refPages.isEmpty())
221        {
222            for (Page referencingPage : refPages)
223            {
224                if (_isAccessibleByReferencingPage(referencingPage, currentAllowAnyConnectedUser, currentAllowedUsers, currentAllowedGroups, currentDeniedUsers, currentDeniedGroups))
225                {
226                    return true;
227                }
228            }
229            return false;
230        }
231        else
232        {
233            // Orphan content
234            return _isAccessibleOrphanContent(content, currentAllowAnyConnectedUser, currentAllowedUsers, currentAllowedGroups, currentDeniedUsers, currentDeniedGroups);
235        }
236    }
237
238    private boolean _isAccessibleByReferencingPage(Page referencingPage, boolean currentAllowAnyConnectedUser, Set<UserIdentity> currentAllowedUsers, Set<GroupIdentity> currentAllowedGroups, Set<UserIdentity> currentDeniedUsers, Set<GroupIdentity> currentDeniedGroups)
239    {
240        if (!_rightManager.hasAnonymousReadAccess(referencingPage))
241        {
242            AllowedUsers refUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(referencingPage);
243            boolean refAllowAnyConnectedUser = refUsersWithReadAccess.isAnyConnectedUserAllowed();
244            Set<UserIdentity> refAllowedUsers = refUsersWithReadAccess.getAllowedUsers();
245            Set<GroupIdentity> refAllowedGroups = refUsersWithReadAccess.getAllowedGroups();
246            Set<UserIdentity> refDeniedUsers = refUsersWithReadAccess.getDeniedUsers();
247            Set<GroupIdentity> refDeniedGroups = refUsersWithReadAccess.getDeniedGroups();
248            
249            // Return true if the content is referenced by a page either accessible either to all connected users
250            // or restricted to the same population as the current page: display the content.
251            return (refAllowAnyConnectedUser
252                        || !currentAllowAnyConnectedUser && refAllowedUsers.containsAll(currentAllowedUsers) && refAllowedGroups.containsAll(currentAllowedGroups))
253                    && currentDeniedUsers.containsAll(refDeniedUsers) && currentDeniedGroups.containsAll(refDeniedGroups);
254        }
255        else
256        {
257            // The content is referenced by a public page
258            return true;
259        }
260    }
261
262    private boolean _isAccessibleOrphanContent(Content content, boolean currentAllowAnyConnectedUser, Set<UserIdentity> currentAllowedUsers, Set<GroupIdentity> currentAllowedGroups, Set<UserIdentity> currentDeniedUsers, Set<GroupIdentity> currentDeniedGroups)
263    {
264        if (!_rightManager.hasAnonymousReadAccess(content))
265        {
266            AllowedUsers refUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(content);
267            boolean refAllowAnyConnectedUser = refUsersWithReadAccess.isAnyConnectedUserAllowed();
268            Set<UserIdentity> refAllowedUsers = refUsersWithReadAccess.getAllowedUsers();
269            Set<GroupIdentity> refAllowedGroups = refUsersWithReadAccess.getAllowedGroups();
270            Set<UserIdentity> refDeniedUsers = refUsersWithReadAccess.getDeniedUsers();
271            Set<GroupIdentity> refDeniedGroups = refUsersWithReadAccess.getDeniedGroups();
272            
273            // Check if the content is accessible to all connected users
274            // or restricted to the same population as the current page: display the content.
275            return (refAllowAnyConnectedUser 
276                        || !currentAllowAnyConnectedUser && refAllowedUsers.containsAll(currentAllowedUsers) && refAllowedGroups.containsAll(currentAllowedGroups))
277                    && currentDeniedUsers.containsAll(refDeniedUsers) && currentDeniedGroups.containsAll(refDeniedGroups);
278        }
279        else
280        {
281            // The content is anonymous read access
282            return true;
283        }
284    }
285}