001/*
002 *  Copyright 2018 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.cms.model.restrictions;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.UUID;
021
022import org.apache.avalon.framework.component.Component;
023import org.apache.avalon.framework.component.ComponentException;
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.ConfigurationException;
026
027import org.ametys.cms.model.restrictions.Restriction.RestrictionResult;
028import org.ametys.cms.repository.Content;
029import org.ametys.plugins.repository.AmetysRepositoryException;
030import org.ametys.runtime.model.Model;
031import org.ametys.runtime.model.ModelItem;
032import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
033
034/**
035 * Helper for definitions with restrictions on contents 
036 */
037public class ContentRestrictedModelItemHelper implements Component
038{
039    /** The Avalon role name */
040    public static final String ROLE = ContentRestrictedModelItemHelper.class.getName();
041    
042    private final Map<String, Map<String, RestrictedModelItem>> _restrictionsToLookup = new HashMap<>();
043    
044    /**
045     * Determine whether a model item can be read at this time.
046     * @param item the restricted model item on which check the restrictions
047     * @param content The content where item is to be written on. Can be null, on content creation. 
048     * @return <code>true</code> if the current user is allowed to write the model item of this content.
049     * @throws AmetysRepositoryException if an error occurs while accessing the content workflow.
050     */
051    @SuppressWarnings("unchecked")
052    public boolean canRead(Content content, RestrictedModelItem item) throws AmetysRepositoryException
053    {
054        Restriction restrictions = item.getRestriction();
055        if (restrictions == null)
056        {
057            return true;
058        }
059        
060        RestrictionResult firstResult = restrictions.canRead(content, item);
061        if (!RestrictionResult.UNKNOWN.equals(firstResult))
062        {
063            return RestrictionResult.TRUE.equals(firstResult);
064        }
065        
066        ModelItem parent = item.getParent();
067        if (parent != null && parent instanceof RestrictedModelItem)
068        {
069            // Check write access on parent model item
070            return ((RestrictedModelItem<Content>) parent).canRead(content);
071        }
072        
073        return true;
074    }
075
076    /**
077     * Determine whether a model item can be written at this time.
078     * @param item the restricted model item on which check the restrictions
079     * @param content The content where item is to be written on. Can be null, on content creation. 
080     * @return <code>true</code> if the current user is allowed to write the model item of this content.
081     * @throws AmetysRepositoryException if an error occurs while accessing the content workflow.
082     */
083    @SuppressWarnings("unchecked")
084    public boolean canWrite(Content content, RestrictedModelItem item) throws AmetysRepositoryException
085    {
086        Restriction restrictions = item.getRestriction();
087        if (restrictions == null)
088        {
089            return true;
090        }
091        
092        RestrictionResult firstResult = restrictions.canWrite(content, item);
093        if (!RestrictionResult.UNKNOWN.equals(firstResult))
094        {
095            return RestrictionResult.TRUE.equals(firstResult);
096        }
097        
098        ModelItem parent = item.getParent();
099        if (parent != null && parent instanceof RestrictedModelItem)
100        {
101            // Check write access on parent model item
102            return ((RestrictedModelItem<Content>) parent).canWrite(content);
103        }
104
105        return canRead(content, item);
106    }
107    
108    /**
109     * Parses the attribute definition's restrictions.
110     * @param pluginName the plugin name
111     * @param attributeConfiguration the attribute configuration to use.
112     * @param restrictedModelItem the restricted model item
113     * @param restrictionManager the restrictions component manager.
114     * @throws ConfigurationException if the configuration is not valid.
115     */
116    @SuppressWarnings("unchecked")
117    public void parseAndSetRestriction(String pluginName, RestrictedModelItem restrictedModelItem, Configuration attributeConfiguration, ThreadSafeComponentManager<Restriction> restrictionManager) throws ConfigurationException
118    {
119        Configuration restrictToConf = attributeConfiguration.getChild("restrict-to", false);
120        
121        if (restrictToConf != null)
122        {
123            Configuration customRestriction = restrictToConf.getChild("custom-restriction", false);
124            String restrictionClassName;
125
126            if (customRestriction != null)
127            {
128                restrictionClassName = customRestriction.getAttribute("class");
129            }
130            else
131            {
132                restrictionClassName = DefaultRestriction.class.getName();
133            }
134            
135            String modelId = restrictedModelItem.getModel().getId();
136            final String restrictionRole = modelId + "$" + restrictedModelItem.getPath() + "$" + UUID.randomUUID().toString();
137            
138            try
139            {
140                Class restrictionClass = Class.forName(restrictionClassName);
141                restrictionManager.addComponent(pluginName, null, restrictionRole, restrictionClass, restrictToConf);
142            }
143            catch (Exception e)
144            {
145                throw new ConfigurationException("Unable to instantiate restrictions for class: " + restrictionClassName, e);
146            }
147            
148            if (!_restrictionsToLookup.containsKey(modelId))
149            {
150                _restrictionsToLookup.put(modelId, new HashMap<>());
151            }
152            
153            Map<String, RestrictedModelItem> restrictionsByModel = _restrictionsToLookup.get(modelId);
154
155            // Will be affected later when restrictionManager will be initialized
156            // in lookupRestrictions() call
157            restrictionsByModel.put(restrictionRole, restrictedModelItem);
158            
159        }
160    }
161    
162    /**
163     * Retrieves local restrictions components and set them into
164     * previously parsed element definition.
165     * @param model the model
166     * @param restrictionManager the restrictions component manager.
167     * @throws Exception if an error occurs.
168     */
169    public void lookupRestrictions(Model model, ThreadSafeComponentManager<Restriction> restrictionManager) throws Exception
170    {
171        restrictionManager.initialize();
172        
173        if (_restrictionsToLookup.containsKey(model.getId()))
174        {
175            for (Map.Entry<String, RestrictedModelItem> entry : _restrictionsToLookup.get(model.getId()).entrySet())
176            {
177                String restrictionRole = entry.getKey();
178                RestrictedModelItem modelItem = entry.getValue();
179                
180                try
181                {
182                    modelItem.setRestriction(restrictionManager.lookup(restrictionRole));
183                }
184                catch (ComponentException e)
185                {
186                    throw new Exception("Unable to lookup restriction role: '" + restrictionRole + "' for model item: " + modelItem, e);
187                }
188            }
189        }
190        
191    }
192}