001/*
002 *  Copyright 2010 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.plugins.workflow.component;
017
018import java.util.List;
019import java.util.Map;
020
021import org.apache.avalon.framework.activity.Disposable;
022import org.apache.avalon.framework.activity.Initializable;
023import org.apache.commons.lang3.StringUtils;
024
025import org.ametys.core.right.Right;
026import org.ametys.core.right.RightManager;
027import org.ametys.core.right.RightManager.RightResult;
028import org.ametys.core.right.RightsExtensionPoint;
029import org.ametys.core.user.UserIdentity;
030import org.ametys.core.util.I18nUtils;
031import org.ametys.plugins.workflow.AbstractWorkflowComponent;
032import org.ametys.plugins.workflow.EnhancedCondition;
033import org.ametys.runtime.i18n.I18nizableText;
034
035import com.opensymphony.module.propertyset.PropertySet;
036import com.opensymphony.workflow.WorkflowException;
037
038/**
039 * Condition for checking rights of an user for the current action.<p>
040 * The following configuration can be used for checking rights:<br>
041 * <pre>
042 * &lt;condition type="avalon"&gt;
043 * &nbsp;&nbsp;&lt;arg name="role"&gt;org.ametys.plugins.workflow.component.CheckRightsCondition&lt;/arg&gt;
044 * &nbsp;&nbsp;&lt;arg name="right"&gt;Right_Edition&lt;/arg&gt;
045 * [&nbsp;&nbsp;&lt;arg name="context"&gt;/cms&lt;/arg&gt;]
046 * &lt;/condition&gt;
047 * </pre>
048 */
049public class CheckRightsCondition extends AbstractWorkflowComponent implements EnhancedCondition, Initializable, Disposable
050{
051    /**
052     * Boolean to force the CheckRightsCondition to returns ok. 
053     */
054    public static final String FORCE = CheckRightsCondition.class.getName() + "$force";
055    
056    /** Default context to use. */
057    protected static final String __DEFAULT_CONTEXT = "/cms";
058    /** Key for getting the right to check. */
059    protected static final String __RIGHT_KEY = "right";
060    /** Key for getting the context to use. */
061    protected static final String __CONTEXT_KEY = "context";
062    /** Rights manager available to subclasses. */
063    protected RightManager _rightManager;
064    /** The rights extension point */ 
065    protected RightsExtensionPoint _rightsExtensionPoint;
066    /** I18nUtils */
067    protected I18nUtils _i18nUtils;
068    
069    @Override
070    public void initialize() throws Exception
071    {
072        _rightManager = (RightManager) _manager.lookup(RightManager.ROLE);
073        _rightsExtensionPoint = (RightsExtensionPoint) _manager.lookup(RightsExtensionPoint.ROLE);
074        _i18nUtils = (I18nUtils) _manager.lookup(I18nUtils.ROLE);
075    }
076
077    @Override
078    public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException
079    {
080        // Retrieve the right to check
081        String rightNeeded = (String) args.get(__RIGHT_KEY);
082        if (rightNeeded == null)
083        {
084            if (_logger.isWarnEnabled())
085            {
086                _logger.warn(String.format("Missing 'right' argument for workflow action id: %d, failing condition",
087                                           transientVars.get("actionId")));
088            }
089            
090            return false;
091        }
092        
093        Boolean force = (Boolean) transientVars.get(FORCE);
094        if (force != null && force)
095        {
096            // Pass condition
097            return true;
098        }
099        
100        // Retrieve the user
101        UserIdentity user = getUser(transientVars);
102        boolean passesCondition = _checkRights(transientVars, args, user, rightNeeded);
103        
104        if (!passesCondition)
105        {
106            List<String> conditionFailures = getConditionFailures(transientVars);
107            if (conditionFailures != null)
108            {
109                conditionFailures.add(String.format("Check right condition failed for workflow action id %d, user '%s' and right '%s'", transientVars.get("actionId"), user, rightNeeded));
110            }
111            
112            addWorkflowError(transientVars, new I18nizableText("plugin.workflow", "WORKFLOW_CHECK_RIGHTS_CONDITION_FAILED"));
113        }
114        return passesCondition;
115    }
116
117    /**
118     * Check if the current user has the needed right.
119     * @param transientVars variables that will not be persisted.
120     * @param args the properties for this function invocation.
121     * @param user the current user.
122     * @param rightNeeded the needed right.
123     * @return <code>true</code> if the user has the right, <code>false</code>
124     *         otherwise.
125     * @throws WorkflowException if an error occurs.
126     */
127    protected boolean _checkRights(Map transientVars, Map args, UserIdentity user, String rightNeeded) throws WorkflowException
128    {
129        // Compute the context
130        Object context = _computeContext(transientVars, args, user, rightNeeded);
131
132        // Check the context presence
133        if (context == null)
134        {
135            if (_logger.isWarnEnabled())
136            {
137                _logger.warn(String.format("Missing context for checking rights for workflow action id: %d, failing condition",
138                                           transientVars.get("actionId")));
139            }
140            
141            return false;
142        }
143        
144        if (rightNeeded.contains("|"))
145        {
146            return checkMultipleOrRights(user, StringUtils.split(rightNeeded, '|'), context);
147        }
148        else if (rightNeeded.contains("&"))
149        {
150            return checkMultipleAndRights(user, StringUtils.split(rightNeeded, '&'), context);
151        }
152        
153        // Check if current user has the right
154        return hasRight(user, rightNeeded, context);
155    }
156    
157    /**
158     * Check that a user has all the given rights on a context (AND condition).
159     * @param user the user.
160     * @param rights the rights to check.
161     * @param context the right context.
162     * @return true if the user has all the rights, false otherwise.
163     */
164    protected boolean checkMultipleAndRights(UserIdentity user, String[] rights, Object context)
165    {
166        for (String right : rights)
167        {
168            if (!hasRight(user, right, context))
169            {
170                return false;
171            }
172        }
173        return true;
174    }
175    
176    /**
177     * Check that a user has at least one of the given rights on a context (OR condition).
178     * @param user the user.
179     * @param rights the rights to check.
180     * @param context the right context.
181     * @return true if the user has at least one right, false otherwise.
182     */
183    protected boolean checkMultipleOrRights(UserIdentity user, String[] rights, Object context)
184    {
185        for (String right : rights)
186        {
187            if (hasRight(user, right, context))
188            {
189                return true;
190            }
191        }
192        return false;
193    }
194    
195    /**
196     * Test if a user has a right on a context.
197     * @param user the user
198     * @param right the right to check.
199     * @param context the right context.
200     * @return true if the user has the right, false otherwise.
201     */
202    protected boolean hasRight(UserIdentity user, String right, Object context)
203    {
204        return _rightManager.hasRight(user, right, context) == RightResult.RIGHT_ALLOW;
205    }
206    
207    /**
208     * Compute the context to use.<br>
209     * Default implementation uses standard context <code>"/cms"</code>.
210     * @param transientVars variables that will not be persisted.
211     * @param args the properties for this function invocation.
212     * @param user the current user.
213     * @param right the needed right.
214     * @return the computed context.
215     * @throws WorkflowException if an error occurs.
216     */
217    protected Object _computeContext(Map transientVars, Map args, UserIdentity user, String right) throws WorkflowException
218    {
219        // Retrieve the context to use
220        String context = (String) args.get(__CONTEXT_KEY);
221
222        if (context == null)
223        {
224            if (_logger.isDebugEnabled())
225            {
226                _logger.debug(String.format("Missing 'context' argument, using default context: %s",
227                                            __DEFAULT_CONTEXT));
228            }
229            
230            return __DEFAULT_CONTEXT;
231        }
232        
233        return context;
234    }
235
236    @Override
237    public void dispose()
238    {
239        _manager.release(_rightManager);
240        _rightManager = null;
241        _manager = null;
242    }
243
244    @Override
245    public List<ConditionArgument> getArguments()
246    {
247        return List.of(new ConditionArgument(__RIGHT_KEY), new ConditionArgument(__CONTEXT_KEY));
248    }
249
250    /**
251     * Get condition description when there are multiple rights involved
252     * @param parameters list of rights
253     * @return the description
254     */
255    protected I18nizableText _getMultipleConditionsDescriptionKey(List<String> parameters)
256    {
257        return new I18nizableText("plugin.workflow", "UITOOL_WORKFLOW_EDITOR_CHECK_RIGHTS_MULTIPLES_CONDITION_DESCRIPTION", parameters);
258    }
259    
260    /**
261     * Get condition description when there is one right
262     * @param parameters the right label as a List
263     * @return the description
264     */
265    protected I18nizableText _getSingleConditionDescriptionKey(List<String> parameters)
266    {
267        return new I18nizableText("plugin.workflow", "UITOOL_WORKFLOW_EDITOR_CHECK_RIGHTS_CONDITION_DESCRIPTION", parameters);
268    }
269    
270    @Override
271    public I18nizableText getDescription(Map<String, String> argumentsValues)
272    {
273        @SuppressWarnings("cast")
274        String rightNeeded = (String) argumentsValues.get(__RIGHT_KEY);
275        String translatedRight = ""; 
276        if (rightNeeded.contains("|"))
277        {
278            String[] rightsOr = StringUtils.split(rightNeeded, '|');
279            translatedRight = "<strong>" + _i18nUtils.translate(_rightsExtensionPoint.getExtension(rightsOr[0]).getLabel()) + "</strong>";
280            for (String rightId: rightsOr)
281            {
282                Right right = _rightsExtensionPoint.getExtension(rightId);
283                translatedRight += _i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_EDITOR_CONDITION_OR")) + "<strong>" + _i18nUtils.translate(right.getDescription()) + "</strong>";
284            }
285            return _getMultipleConditionsDescriptionKey(List.of(translatedRight));
286        }
287        else if (rightNeeded.contains("&"))
288        {
289            String[] rightsAnd = StringUtils.split(rightNeeded, '&');
290            translatedRight = "<strong>" + _i18nUtils.translate(_rightsExtensionPoint.getExtension(rightsAnd[0]).getLabel()) + "</strong>";
291            for (String rightId: rightsAnd)
292            {
293                Right right = _rightsExtensionPoint.getExtension(rightId);
294                translatedRight += _i18nUtils.translate(new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_EDITOR_CONDITION_AND")) + "<strong>" + _i18nUtils.translate(right.getDescription()) + "</strong>";
295            }
296            return _getMultipleConditionsDescriptionKey(List.of(translatedRight));
297        }
298        Right right = _rightsExtensionPoint.getExtension(rightNeeded);
299        translatedRight = "<strong>" + _i18nUtils.translate(right.getLabel()) + "</strong>";
300        return _getSingleConditionDescriptionKey(List.of(translatedRight));
301    }
302}