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 appears on pages with limited access, and the given user can access at least one.
059         */
060        ALLOWED,
061        
062        /**
063         * The content appears on pages with limited access, but the given user can't access any.
064         */
065        FORBIDDEN
066    }
067    
068    /** The component role. */
069    public static final String ROLE = ContentAccessManager.class.getName();
070    
071    /** The right manager */
072    protected RightManager _rightManager;
073    
074    /** The current user provider */
075    protected CurrentUserProvider _currentUserProvider;
076    
077    /** The component handling profile storage */
078    protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
079    
080    @Override
081    public void service(ServiceManager manager) throws ServiceException
082    {
083        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
084        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
085        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) manager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
086    }
087    
088    /**
089     * Get the access status of a content, from the user currently connected to the front-office.
090     * @param content the content to test.
091     * @return the content access status.
092     */
093    public ContentAccess getAccess(Content content)
094    {
095        return getAccess(content, true);
096    }
097    
098    /**
099     * Get the access status of a content, from the user currently connected to the front-office.
100     * @param content the content to test.
101     * @param checkUser When true, the user's ability to view the page will be checked
102     * ({@link ContentAccess#ALLOWED} may be returned).<br>
103     * When false, only the content general availability will be checked: {@link ContentAccess#ALLOWED} will never be returned.
104     * @return the content access status.
105     */
106    public ContentAccess getAccess(Content content, boolean checkUser)
107    {
108        return getAccess(content, _currentUserProvider.getUser(), checkUser);
109    }
110    
111    /**
112     * Get the access status of a content from a given user.
113     * @param content the content to test.
114     * @param userIdentity the user identity, can be null (to test anonymous access).
115     * @return the content access status.
116     */
117    public ContentAccess getAccess(Content content, UserIdentity userIdentity)
118    {
119        return getAccess(content, userIdentity, true);
120    }
121    
122    /**
123     * Get the access status of a content from a given user.
124     * The user access is check ONLY on the content's pages. Has read access on content is not enough ! 
125     * @param content the content to test.
126     * @param userIdentity the user identity, can be null (to test anonymous access).
127     * @param checkUser When true, the user's ability to view the page will be checked
128     * ({@link ContentAccess#ALLOWED} may be returned).<br>
129     * When false, only the content general availability will be checked: {@link ContentAccess#ALLOWED} will never be returned.
130     * @return the content access status.
131     */
132    public ContentAccess getAccess(Content content, UserIdentity userIdentity, boolean checkUser)
133    {
134        if (!(content instanceof WebContent))
135        {
136            return _getContentAccess(content, userIdentity, checkUser);
137        }
138        
139        WebContent webContent = (WebContent) content;
140        Collection<Page> contentPages = webContent.getReferencingPages();
141        
142        // Test the restrictions.
143        if (contentPages.isEmpty())
144        {
145            return _getContentAccess(content, userIdentity, checkUser);
146        }
147        else
148        {
149            for (Page page : contentPages)
150            {
151                // If the content is present in a page that is not restricted, it's visible by all.
152                if (_rightManager.hasAnonymousReadAccess(page))
153                {
154                    return ContentAccess.UNRESTRICTED;
155                }
156                
157                // If the user is allowed to see at least one page containing the content, he can access the content.
158                if (checkUser && _rightManager.hasReadAccess(userIdentity, page))
159                {
160                    return ContentAccess.ALLOWED;
161                }
162            }
163        }
164        
165        return ContentAccess.FORBIDDEN;
166    }
167    
168    private ContentAccess _getContentAccess(Content content, UserIdentity userIdentity, boolean checkUser)
169    {
170        // Returns content access regardless of its pages.
171        
172        if (_rightManager.hasAnonymousReadAccess(content))
173        {
174            return ContentAccess.UNRESTRICTED;
175        }
176        
177        return checkUser && _rightManager.hasReadAccess(userIdentity, content) ? ContentAccess.ALLOWED : ContentAccess.FORBIDDEN;
178    }
179    
180    /**
181     * Test if the content is displayed in a page having the same access rights as the current page.
182     * @param content the web content.
183     * @param currentPage the current page.
184     * @return true if the content is accessible, false otherwise.
185     */
186    public boolean isAccessibleByPage(WebContent content, Page currentPage)
187    {
188        if (!_rightManager.hasAnonymousReadAccess(currentPage))
189        {
190            AllowedUsers currentUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(currentPage);
191            boolean currentAllowAnyConnectedUser = currentUsersWithReadAccess.isAnyConnectedUserAllowed();
192            Set<UserIdentity> currentAllowedUsers = currentUsersWithReadAccess.getAllowedUsers();
193            Set<GroupIdentity> currentAllowedGroups = currentUsersWithReadAccess.getAllowedGroups();
194            Set<UserIdentity> currentDeniedUsers = currentUsersWithReadAccess.getDeniedUsers();
195            Set<GroupIdentity> currentDeniedGroups = currentUsersWithReadAccess.getDeniedGroups();
196            
197            Collection<Page> refPages = content.getReferencingPages();
198            if (!refPages.isEmpty())
199            {
200                for (Page referencingPage : refPages)
201                {
202                    if (!_rightManager.hasAnonymousReadAccess(referencingPage))
203                    {
204                        AllowedUsers refUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(referencingPage);
205                        boolean refAllowAnyConnectedUser = refUsersWithReadAccess.isAnyConnectedUserAllowed();
206                        Set<UserIdentity> refAllowedUsers = refUsersWithReadAccess.getAllowedUsers();
207                        Set<GroupIdentity> refAllowedGroups = refUsersWithReadAccess.getAllowedGroups();
208                        Set<UserIdentity> refDeniedUsers = refUsersWithReadAccess.getDeniedUsers();
209                        Set<GroupIdentity> refDeniedGroups = refUsersWithReadAccess.getDeniedGroups();
210                        
211                        if ((refAllowAnyConnectedUser
212                                    || !currentAllowAnyConnectedUser && refAllowedUsers.containsAll(currentAllowedUsers) && refAllowedGroups.containsAll(currentAllowedGroups))
213                                && currentDeniedUsers.containsAll(refDeniedUsers) && currentDeniedGroups.containsAll(refDeniedGroups))
214                        {
215                            // The content is referenced by a page either accessible either to all connected users
216                            // or restricted to the same population as the current page: display the content.
217                            return true;
218                        }
219                    }
220                    else
221                    {
222                        // The content is referenced by a public page: display the content.
223                        return true;
224                    }
225                }
226                
227                // The content isn't referenced by a page with the same access: do not display the content.
228                return false;
229            }
230            else
231            {
232                // Orphan content
233                if (!_rightManager.hasAnonymousReadAccess(content))
234                {
235                    AllowedUsers refUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(content);
236                    boolean refAllowAnyConnectedUser = refUsersWithReadAccess.isAnyConnectedUserAllowed();
237                    Set<UserIdentity> refAllowedUsers = refUsersWithReadAccess.getAllowedUsers();
238                    Set<GroupIdentity> refAllowedGroups = refUsersWithReadAccess.getAllowedGroups();
239                    Set<UserIdentity> refDeniedUsers = refUsersWithReadAccess.getDeniedUsers();
240                    Set<GroupIdentity> refDeniedGroups = refUsersWithReadAccess.getDeniedGroups();
241                    
242                    // Check if the content is accessible to all connected users
243                    // or restricted to the same population as the current page: display the content.
244                    return (refAllowAnyConnectedUser 
245                                || !currentAllowAnyConnectedUser && refAllowedUsers.containsAll(currentAllowedUsers) && refAllowedGroups.containsAll(currentAllowedGroups))
246                            && currentDeniedUsers.containsAll(refDeniedUsers) && currentDeniedGroups.containsAll(refDeniedGroups);
247                }
248                else
249                {
250                    // The content is anonymous read access: display the content.
251                    return true;
252                }
253            }
254        }
255        else
256        {
257            // The current page is not restricted: display only unrestricted contents
258            // N.B: the current user's right are *NOT* tested, as in this mode, the results are cached.
259            return getAccess(content, null, false) == ContentAccess.UNRESTRICTED;
260        }
261    }
262}