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            for (Page referencingPage : content.getReferencingPages())
198            {
199                if (!_rightManager.hasAnonymousReadAccess(referencingPage))
200                {
201                    AllowedUsers refUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(referencingPage);
202                    boolean refAllowAnyConnectedUser = refUsersWithReadAccess.isAnyConnectedUserAllowed();
203                    Set<UserIdentity> refAllowedUsers = refUsersWithReadAccess.getAllowedUsers();
204                    Set<GroupIdentity> refAllowedGroups = refUsersWithReadAccess.getAllowedGroups();
205                    Set<UserIdentity> refDeniedUsers = refUsersWithReadAccess.getDeniedUsers();
206                    Set<GroupIdentity> refDeniedGroups = refUsersWithReadAccess.getDeniedGroups();
207                    
208                    if ((refAllowAnyConnectedUser || (!currentAllowAnyConnectedUser && refAllowedUsers.containsAll(currentAllowedUsers) && refAllowedGroups.containsAll(currentAllowedGroups)))
209                            && currentDeniedUsers.containsAll(refDeniedUsers) && currentDeniedGroups.containsAll(refDeniedGroups))
210                    {
211                        // The content is referenced by a page either accessible either to all connected users
212                        // or restricted to the same population as the current page: display the content.
213                        return true;
214                    }
215                }
216                else
217                {
218                    // The content is referenced by a public page: display the content.
219                    return true;
220                }
221            }
222            
223            // The content isn't referenced by a page with the same access: do not display the content.
224            return false;
225        }
226        else
227        {
228            // The current page is not restricted: display only unrestricted contents
229            // N.B: the current user's right are *NOT* tested, as in this mode, the results are cached.
230            return getAccess(content, null, false) == ContentAccess.UNRESTRICTED;
231        }
232    }
233}