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;
036import org.ametys.web.repository.sitemap.Sitemap;
037
038/**
039 * Class managing access to contents on the front-office side.
040 */
041public class ContentAccessManager extends AbstractLogEnabled implements Component, Serviceable
042{
043    
044    /**
045     * Enumeration representing a content access status.
046     */
047    public enum ContentAccess 
048    {
049        /**
050         * The content can be viewed by all users because of one of these reasons:
051         * <ul>
052         *   <li>it appears on a public page</li>
053         *   <li>it doesn't appear on any page (orphan content) but its sitemap is freely accessible</li>
054         * </ul>
055         */
056        UNRESTRICTED,
057        
058        /**
059         * The content appears on pages with limited access, and the given user can access at least one.
060         */
061        ALLOWED,
062        
063        /**
064         * The content appears on pages with limited access, but the given user can't access any.
065         */
066        FORBIDDEN
067    }
068    
069    /** The component role. */
070    public static final String ROLE = ContentAccessManager.class.getName();
071    
072    /** The right manager */
073    protected RightManager _rightManager;
074    
075    /** The current user provider */
076    protected CurrentUserProvider _currentUserProvider;
077    
078    /** The component handling profile storage */
079    protected ProfileAssignmentStorageExtensionPoint _profileAssignmentStorageEP;
080    
081    @Override
082    public void service(ServiceManager manager) throws ServiceException
083    {
084        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
085        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
086        _profileAssignmentStorageEP = (ProfileAssignmentStorageExtensionPoint) manager.lookup(ProfileAssignmentStorageExtensionPoint.ROLE);
087    }
088    
089    /**
090     * Get the access status of a content, from the user currently connected to the front-office.
091     * @param content the content to test.
092     * @return the content access status.
093     */
094    public ContentAccess getAccess(Content content)
095    {
096        return getAccess(content, true);
097    }
098    
099    /**
100     * Get the access status of a content, from the user currently connected to the front-office.
101     * @param content the content to test.
102     * @param checkUser When true, the user's ability to view the page will be checked
103     * ({@link ContentAccess#ALLOWED} may be returned).<br>
104     * When false, only the content general availability will be checked: {@link ContentAccess#ALLOWED} will never be returned.
105     * @return the content access status.
106     */
107    public ContentAccess getAccess(Content content, boolean checkUser)
108    {
109        return getAccess(content, _currentUserProvider.getUser(), checkUser);
110    }
111    
112    /**
113     * Get the access status of a content from a given user.
114     * @param content the content to test.
115     * @param userIdentity the user identity, can be null (to test anonymous access).
116     * @return the content access status.
117     */
118    public ContentAccess getAccess(Content content, UserIdentity userIdentity)
119    {
120        return getAccess(content, userIdentity, true);
121    }
122    
123    /**
124     * Get the access status of a content from a given user.
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        // Objects other than web contents cannot be restricted.
135        if (!(content instanceof WebContent))
136        {
137            return ContentAccess.UNRESTRICTED;
138        }
139        
140        WebContent webContent = (WebContent) content;
141        Collection<Page> contentPages = webContent.getReferencingPages();
142        
143        boolean isRestricted = true;
144        boolean allowed = false;
145        
146        // Test the restrictions.
147        if (contentPages.isEmpty())
148        {
149            // Test on the sitemap 
150            String language = webContent.getLanguage();
151            Sitemap sitemap = webContent.getSite().getSitemap(language);
152            
153            isRestricted = !_rightManager.hasAnonymousReadAccess(sitemap);
154            if (checkUser)
155            {
156                allowed = _rightManager.hasReadAccess(userIdentity, sitemap);
157            }
158        }
159        else
160        {
161            for (Page page : contentPages)
162            {
163                // If the content is present in a page that is not restricted, it's visible by all.
164                if (_rightManager.hasAnonymousReadAccess(page))
165                {
166                    isRestricted = false;
167                }
168                
169                // If the user is allowed to see at least one page containing the content, he can access the content.
170                if (checkUser && _rightManager.hasReadAccess(userIdentity, page))
171                {
172                    allowed = true;
173                }
174            }
175        }
176        
177        if (!isRestricted)
178        {
179            return ContentAccess.UNRESTRICTED;
180        }
181        else if (allowed)
182        {
183            return ContentAccess.ALLOWED;
184        }
185        else
186        {
187            return ContentAccess.FORBIDDEN;
188        }
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            AllowedUsers currentUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(currentPage);
202            boolean currentAllowAnyConnectedUser = currentUsersWithReadAccess.isAnyConnectedUserAllowed();
203            Set<UserIdentity> currentAllowedUsers = currentUsersWithReadAccess.getAllowedUsers();
204            Set<GroupIdentity> currentAllowedGroups = currentUsersWithReadAccess.getAllowedGroups();
205            Set<UserIdentity> currentDeniedUsers = currentUsersWithReadAccess.getDeniedUsers();
206            Set<GroupIdentity> currentDeniedGroups = currentUsersWithReadAccess.getDeniedGroups();
207            
208            for (Page referencingPage : content.getReferencingPages())
209            {
210                if (!_rightManager.hasAnonymousReadAccess(referencingPage))
211                {
212                    AllowedUsers refUsersWithReadAccess = _rightManager.getReadAccessAllowedUsers(referencingPage);
213                    boolean refAllowAnyConnectedUser = refUsersWithReadAccess.isAnyConnectedUserAllowed();
214                    Set<UserIdentity> refAllowedUsers = refUsersWithReadAccess.getAllowedUsers();
215                    Set<GroupIdentity> refAllowedGroups = refUsersWithReadAccess.getAllowedGroups();
216                    Set<UserIdentity> refDeniedUsers = refUsersWithReadAccess.getDeniedUsers();
217                    Set<GroupIdentity> refDeniedGroups = refUsersWithReadAccess.getDeniedGroups();
218                    
219                    if ((refAllowAnyConnectedUser || (!currentAllowAnyConnectedUser && refAllowedUsers.containsAll(currentAllowedUsers) && refAllowedGroups.containsAll(currentAllowedGroups)))
220                            && currentDeniedUsers.containsAll(refDeniedUsers) && currentDeniedGroups.containsAll(refDeniedGroups))
221                    {
222                        // The content is referenced by a page either accessible either to all connected users
223                        // or restricted to the same population as the current page: display the content.
224                        return true;
225                    }
226                }
227                else
228                {
229                    // The content is referenced by a public page: display the content.
230                    return true;
231                }
232            }
233            
234            // The content isn't referenced by a page with the same access: do not display the content.
235            return false;
236        }
237        else
238        {
239            // The current page is not restricted: display only unrestricted contents
240            // N.B: the current user's right are *NOT* tested, as in this mode, the results are cached.
241            return getAccess(content, null, false) == ContentAccess.UNRESTRICTED;
242        }
243    }
244}