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