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