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        
144        WebContent webContent = (WebContent) content;
145        Collection<Page> contentPages = webContent.getReferencingPages();
146        
147        // Test the restrictions.
148        if (contentPages.isEmpty())
149        {
150            return _getContentAccess(content, userIdentity, checkUser);
151        }
152        else
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                
162                if (_rightManager.hasAnyConnectedUserReadAccess(page))
163                {
164                    return ContentAccess.ANYCONNECTED_ALLOWED;
165                }
166                
167                // If the user is allowed to see at least one page containing the content, he can access the content.
168                if (checkUser && _rightManager.hasReadAccess(userIdentity, page))
169                {
170                    return ContentAccess.ALLOWED;
171                }
172            }
173        }
174        
175        return ContentAccess.FORBIDDEN;
176    }
177    
178    private ContentAccess _getContentAccess(Content content, UserIdentity userIdentity, boolean checkUser)
179    {
180        // Returns content access regardless of its pages.
181        
182        if (_rightManager.hasAnonymousReadAccess(content))
183        {
184            return ContentAccess.UNRESTRICTED;
185        }
186        
187        if (_rightManager.hasAnyConnectedUserReadAccess(content))
188        {
189            return ContentAccess.ANYCONNECTED_ALLOWED;
190        }
191        
192        return checkUser && _rightManager.hasReadAccess(userIdentity, content) ? ContentAccess.ALLOWED : ContentAccess.FORBIDDEN;
193    }
194    
195    /**
196     * Test if the content is displayed in a page having the same access rights as the current page.
197     * @param content the web content.
198     * @param currentPage the current page.
199     * @return true if the content is accessible, false otherwise.
200     */
201    public boolean isAccessibleByPage(WebContent content, Page currentPage)
202    {
203        if (!_rightManager.hasAnonymousReadAccess(currentPage))
204        {
205            AllowedUsers currentUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(currentPage);
206            boolean currentAllowAnyConnectedUser = currentUsersWithReadAccess.isAnyConnectedUserAllowed();
207            Set<UserIdentity> currentAllowedUsers = currentUsersWithReadAccess.getAllowedUsers();
208            Set<GroupIdentity> currentAllowedGroups = currentUsersWithReadAccess.getAllowedGroups();
209            Set<UserIdentity> currentDeniedUsers = currentUsersWithReadAccess.getDeniedUsers();
210            Set<GroupIdentity> currentDeniedGroups = currentUsersWithReadAccess.getDeniedGroups();
211            
212            Collection<Page> refPages = content.getReferencingPages();
213            if (!refPages.isEmpty())
214            {
215                for (Page referencingPage : refPages)
216                {
217                    if (!_rightManager.hasAnonymousReadAccess(referencingPage))
218                    {
219                        AllowedUsers refUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(referencingPage);
220                        boolean refAllowAnyConnectedUser = refUsersWithReadAccess.isAnyConnectedUserAllowed();
221                        Set<UserIdentity> refAllowedUsers = refUsersWithReadAccess.getAllowedUsers();
222                        Set<GroupIdentity> refAllowedGroups = refUsersWithReadAccess.getAllowedGroups();
223                        Set<UserIdentity> refDeniedUsers = refUsersWithReadAccess.getDeniedUsers();
224                        Set<GroupIdentity> refDeniedGroups = refUsersWithReadAccess.getDeniedGroups();
225                        
226                        if ((refAllowAnyConnectedUser
227                                    || !currentAllowAnyConnectedUser && refAllowedUsers.containsAll(currentAllowedUsers) && refAllowedGroups.containsAll(currentAllowedGroups))
228                                && currentDeniedUsers.containsAll(refDeniedUsers) && currentDeniedGroups.containsAll(refDeniedGroups))
229                        {
230                            // The content is referenced by a page either accessible either to all connected users
231                            // or restricted to the same population as the current page: display the content.
232                            return true;
233                        }
234                    }
235                    else
236                    {
237                        // The content is referenced by a public page: display the content.
238                        return true;
239                    }
240                }
241                
242                // The content isn't referenced by a page with the same access: do not display the content.
243                return false;
244            }
245            else
246            {
247                // Orphan content
248                if (!_rightManager.hasAnonymousReadAccess(content))
249                {
250                    AllowedUsers refUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(content);
251                    boolean refAllowAnyConnectedUser = refUsersWithReadAccess.isAnyConnectedUserAllowed();
252                    Set<UserIdentity> refAllowedUsers = refUsersWithReadAccess.getAllowedUsers();
253                    Set<GroupIdentity> refAllowedGroups = refUsersWithReadAccess.getAllowedGroups();
254                    Set<UserIdentity> refDeniedUsers = refUsersWithReadAccess.getDeniedUsers();
255                    Set<GroupIdentity> refDeniedGroups = refUsersWithReadAccess.getDeniedGroups();
256                    
257                    // Check if the content is accessible to all connected users
258                    // or restricted to the same population as the current page: display the content.
259                    return (refAllowAnyConnectedUser 
260                                || !currentAllowAnyConnectedUser && refAllowedUsers.containsAll(currentAllowedUsers) && refAllowedGroups.containsAll(currentAllowedGroups))
261                            && currentDeniedUsers.containsAll(refDeniedUsers) && currentDeniedGroups.containsAll(refDeniedGroups);
262                }
263                else
264                {
265                    // The content is anonymous read access: display the content.
266                    return true;
267                }
268            }
269        }
270        else
271        {
272            // The current page is not restricted: display only unrestricted contents
273            // N.B: the current user's right are *NOT* tested, as in this mode, the results are cached.
274            return getAccess(content, null, false) == ContentAccess.UNRESTRICTED;
275        }
276    }
277}