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.RightManager;
026import org.ametys.core.right.RightManager.RightResult;
027import org.ametys.core.user.UserIdentity;
028import org.ametys.plugins.workflow.AbstractWorkflowComponent;
029import org.ametys.runtime.i18n.I18nizableText;
030
031import com.opensymphony.module.propertyset.PropertySet;
032import com.opensymphony.workflow.Condition;
033import com.opensymphony.workflow.WorkflowException;
034
035/**
036 * Condition for checking rights of an user for the current action.<p>
037 * The following configuration can be used for checking rights:<br>
038 * <pre>
039 * &lt;condition type="avalon"&gt;
040 * &nbsp;&nbsp;&lt;arg name="role"&gt;org.ametys.plugins.workflow.component.CheckRightsCondition&lt;/arg&gt;
041 * &nbsp;&nbsp;&lt;arg name="right"&gt;Right_Edition&lt;/arg&gt;
042 * [&nbsp;&nbsp;&lt;arg name="context"&gt;/cms&lt;/arg&gt;]
043 * &lt;/condition&gt;
044 * </pre>
045 */
046public class CheckRightsCondition extends AbstractWorkflowComponent implements Condition, Initializable, Disposable
047{
048    /**
049     * Boolean to force the CheckRightsCondition to returns ok. 
050     */
051    public static final String FORCE = CheckRightsCondition.class.getName() + "$force";
052    
053    /** Default context to use. */
054    protected static final String __DEFAULT_CONTEXT = "/cms";
055    /** Key for getting the right to check. */
056    protected static final String __RIGHT_KEY = "right";
057    /** Key for getting the context to use. */
058    protected static final String __CONTEXT_KEY = "context";
059    /** Rights manager available to subclasses. */
060    protected RightManager _rightManager;
061
062    @Override
063    public void initialize() throws Exception
064    {
065        _rightManager = (RightManager) _manager.lookup(RightManager.ROLE);
066    }
067
068    @Override
069    public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException
070    {
071        // Retrieve the right to check
072        String rightNeeded = (String) args.get(__RIGHT_KEY);
073        if (rightNeeded == null)
074        {
075            if (_logger.isWarnEnabled())
076            {
077                _logger.warn(String.format("Missing 'right' argument for workflow action id: %d, failing condition",
078                                           transientVars.get("actionId")));
079            }
080            
081            return false;
082        }
083        
084        Boolean force = (Boolean) transientVars.get(FORCE);
085        if (force != null && force)
086        {
087            // Pass condition
088            return true;
089        }
090        
091        // Retrieve the user
092        UserIdentity user = getUser(transientVars);
093        boolean passesCondition = _checkRights(transientVars, args, user, rightNeeded);
094        
095        if (!passesCondition)
096        {
097            List<String> conditionFailures = getConditionFailures(transientVars);
098            if (conditionFailures != null)
099            {
100                conditionFailures.add(String.format("Check right condition failed for workflow action id %d, user '%s' and right '%s'", transientVars.get("actionId"), user, rightNeeded));
101            }
102            
103            addWorkflowError(transientVars, new I18nizableText("plugin.workflow", "WORKFLOW_CHECK_RIGHTS_CONDITION_FAILED"));
104        }
105        return passesCondition;
106    }
107
108    /**
109     * Check if the current user has the needed right.
110     * @param transientVars variables that will not be persisted.
111     * @param args the properties for this function invocation.
112     * @param user the current user.
113     * @param rightNeeded the needed right.
114     * @return <code>true</code> if the user has the right, <code>false</code>
115     *         otherwise.
116     * @throws WorkflowException if an error occurs.
117     */
118    protected boolean _checkRights(Map transientVars, Map args, UserIdentity user, String rightNeeded) throws WorkflowException
119    {
120        // Compute the context
121        Object context = _computeContext(transientVars, args, user, rightNeeded);
122
123        // Check the context presence
124        if (context == null)
125        {
126            if (_logger.isWarnEnabled())
127            {
128                _logger.warn(String.format("Missing context for checking rights for workflow action id: %d, failing condition",
129                                           transientVars.get("actionId")));
130            }
131            
132            return false;
133        }
134        
135        if (rightNeeded.contains("|"))
136        {
137            return checkMultipleOrRights(user, StringUtils.split(rightNeeded, '|'), context);
138        }
139        else if (rightNeeded.contains("&"))
140        {
141            return checkMultipleAndRights(user, StringUtils.split(rightNeeded, '&'), context);
142        }
143        
144        // Check if current user has the right
145        return hasRight(user, rightNeeded, context);
146    }
147    
148    /**
149     * Check that a user has all the given rights on a context (AND condition).
150     * @param user the user.
151     * @param rights the rights to check.
152     * @param context the right context.
153     * @return true if the user has all the rights, false otherwise.
154     */
155    protected boolean checkMultipleAndRights(UserIdentity user, String[] rights, Object context)
156    {
157        for (String right : rights)
158        {
159            if (!hasRight(user, right, context))
160            {
161                return false;
162            }
163        }
164        return true;
165    }
166    
167    /**
168     * Check that a user has at least one of the given rights on a context (OR condition).
169     * @param user the user.
170     * @param rights the rights to check.
171     * @param context the right context.
172     * @return true if the user has at least one right, false otherwise.
173     */
174    protected boolean checkMultipleOrRights(UserIdentity user, String[] rights, Object context)
175    {
176        for (String right : rights)
177        {
178            if (hasRight(user, right, context))
179            {
180                return true;
181            }
182        }
183        return false;
184    }
185    
186    /**
187     * Test if a user has a right on a context.
188     * @param user the user
189     * @param right the right to check.
190     * @param context the right context.
191     * @return true if the user has the right, false otherwise.
192     */
193    protected boolean hasRight(UserIdentity user, String right, Object context)
194    {
195        return _rightManager.hasRight(user, right, context) == RightResult.RIGHT_ALLOW;
196    }
197    
198    /**
199     * Compute the context to use.<br>
200     * Default implementation uses standard context <code>"/cms"</code>.
201     * @param transientVars variables that will not be persisted.
202     * @param args the properties for this function invocation.
203     * @param user the current user.
204     * @param right the needed right.
205     * @return the computed context.
206     * @throws WorkflowException if an error occurs.
207     */
208    protected Object _computeContext(Map transientVars, Map args, UserIdentity user, String right) throws WorkflowException
209    {
210        // Retrieve the context to use
211        String context = (String) args.get(__CONTEXT_KEY);
212
213        if (context == null)
214        {
215            if (_logger.isDebugEnabled())
216            {
217                _logger.debug(String.format("Missing 'context' argument, using default context: %s",
218                                            __DEFAULT_CONTEXT));
219            }
220            
221            return __DEFAULT_CONTEXT;
222        }
223        
224        return context;
225    }
226
227    @Override
228    public void dispose()
229    {
230        _manager.release(_rightManager);
231        _rightManager = null;
232        _manager = null;
233    }
234}