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.List; 019import java.util.Set; 020 021import org.apache.avalon.framework.component.Component; 022import org.apache.avalon.framework.configuration.Configuration; 023import org.apache.avalon.framework.configuration.ConfigurationException; 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.avalon.framework.service.Serviceable; 027 028import org.ametys.cms.contenttype.DefaultContentType; 029import org.ametys.cms.repository.Content; 030import org.ametys.cms.repository.WorkflowAwareContent; 031import org.ametys.core.right.RightManager; 032import org.ametys.core.right.RightManager.RightResult; 033import org.ametys.core.user.CurrentUserProvider; 034import org.ametys.core.user.UserIdentity; 035import org.ametys.plugins.repository.AmetysRepositoryException; 036import org.ametys.plugins.workflow.support.WorkflowProvider; 037import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow; 038import org.ametys.runtime.model.ModelItem; 039 040import com.opensymphony.workflow.spi.Step; 041 042/** 043 * Helper for definitions with restrictions on contents 044 */ 045public class ContentRestrictedModelItemHelper implements Component, Serviceable 046{ 047 /** The Avalon role name */ 048 public static final String ROLE = ContentRestrictedModelItemHelper.class.getName(); 049 050 /** The rights manager. */ 051 private RightManager _rightManager; 052 /** Current user provider. */ 053 private CurrentUserProvider _currentUserProvider; 054 /** The workflow provider */ 055 private WorkflowProvider _workflowProvider; 056 057 /** 058 * TODO NEWATTRIBUTEAPI_CONTENT: make this enum private when caller of {@link #_doFirstRestrictionsChecks(Content, Restrictions, boolean)} in DefaultContentType are removed 059 */ 060 public enum FirstRestrictionsChecksState 061 { 062 /** First checks are OK */ 063 TRUE, 064 065 /** First checks are not OK */ 066 FALSE, 067 068 /** There are more checks to do */ 069 UNKNOWN, 070 } 071 072 public void service(ServiceManager manager) throws ServiceException 073 { 074 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 075 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 076 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 077 } 078 079 /** 080 * Determine whether a model item can be read at this time. 081 * @param item the model item on which check the restrictions 082 * @param content The content where item is to be written on. Can be null, on content creation. 083 * @param restrictions the restrictions to check 084 * @return <code>true</code> if the current user is allowed to write the model item of this content. 085 * @throws AmetysRepositoryException if an error occurs while accessing the content. 086 */ 087 @SuppressWarnings("unchecked") 088 public boolean canRead(Content content, ModelItem item, Restrictions restrictions) throws AmetysRepositoryException 089 { 090 FirstRestrictionsChecksState state = _doFirstRestrictionsChecks(content, restrictions, true); 091 if (!FirstRestrictionsChecksState.UNKNOWN.equals(state)) 092 { 093 return FirstRestrictionsChecksState.TRUE.equals(state); 094 } 095 096 ModelItem parent = item.getParent(); 097 if (parent != null && parent instanceof RestrictedModelItem) 098 { 099 // Check write access on parent model item 100 return ((RestrictedModelItem<Content>) parent).canRead(content); 101 } 102 103 return true; 104 } 105 106 /** 107 * Determine whether a model item can be written at this time. 108 * @param item the model item on which check the restrictions 109 * @param content The content where item is to be written on. Can be null, on content creation. 110 * @param restrictions the restrictions to check 111 * @return <code>true</code> if the current user is allowed to write the model item of this content. 112 * @throws AmetysRepositoryException if an error occurs while accessing the content. 113 */ 114 @SuppressWarnings("unchecked") 115 public boolean canWrite(Content content, ModelItem item, Restrictions restrictions) throws AmetysRepositoryException 116 { 117 FirstRestrictionsChecksState state = _doFirstRestrictionsChecks(content, restrictions, false); 118 if (!FirstRestrictionsChecksState.UNKNOWN.equals(state)) 119 { 120 return FirstRestrictionsChecksState.TRUE.equals(state); 121 } 122 123 ModelItem parent = item.getParent(); 124 if (parent != null && parent instanceof RestrictedModelItem) 125 { 126 // Check write access on parent model item 127 return ((RestrictedModelItem<Content>) parent).canWrite(content); 128 } 129 130 return canRead(content, item, restrictions); 131 } 132 133 /** 134 * Does the first checks on restrictions 135 * TODO NEWATTRIBUTEAPI_CONTENT: make this method private when caller in DefaultContentType are removed 136 * @param content The content where item is to be read / written on. Can be null, on content creation. 137 * @param restrictions The restrictions to apply 138 * @param forReading <code>true</code> for reading checks, <code>false</code> for writting checks. 139 * @return the state of the first checks 140 */ 141 public FirstRestrictionsChecksState _doFirstRestrictionsChecks(Content content, Restrictions restrictions, boolean forReading) 142 { 143 if (restrictions == null) 144 { 145 return FirstRestrictionsChecksState.TRUE; 146 } 147 148 if (forReading && restrictions.cannotRead() || !forReading && restrictions.cannotWrite()) 149 { 150 return FirstRestrictionsChecksState.FALSE; 151 } 152 153 if (content == null) 154 { 155 // Unable to check right (content is not yet created), assume user has right 156 return FirstRestrictionsChecksState.TRUE; 157 } 158 159 boolean hasRights = _hasRights(content, forReading ? restrictions.getReadRightIds() : restrictions.getWriteRightIds()); 160 161 if (!hasRights) 162 { 163 return FirstRestrictionsChecksState.FALSE; 164 } 165 166 if (content instanceof WorkflowAwareContent) 167 { 168 hasRights = _isInWorkflowStep((WorkflowAwareContent) content, forReading ? restrictions.getReadWorkflowfStepIds() : restrictions.getWriteWorkflowfStepIds()); 169 170 if (!hasRights) 171 { 172 return FirstRestrictionsChecksState.FALSE; 173 } 174 } 175 176 return FirstRestrictionsChecksState.UNKNOWN; 177 } 178 179 /** 180 * Check if current user has the given rights. 181 * @param rightLimitations the right limitations. 182 * @param content the content. 183 * @return <code>true</code> if it is on at least one step, 184 * <code>false</code> otherwise. 185 */ 186 private boolean _hasRights(Content content, Set<String> rightLimitations) 187 { 188 if (rightLimitations.isEmpty()) 189 { 190 return true; 191 } 192 193 UserIdentity user = _currentUserProvider.getUser(); 194 195 for (String rightId : rightLimitations) 196 { 197 if (_rightManager.hasRight(user, rightId, content) == RightResult.RIGHT_ALLOW) 198 { 199 return true; 200 } 201 } 202 203 return false; 204 } 205 206 /** 207 * Check if the workflow of the content is in a given current step. 208 * @param workflowLimitations the workflow limitations. 209 * @param content the content. 210 * @return <code>true</code> if it is on at least one step, 211 * <code>false</code> otherwise. 212 * @throws AmetysRepositoryException if an error occurs. 213 */ 214 private boolean _isInWorkflowStep(WorkflowAwareContent content, Set<Integer> workflowLimitations) throws AmetysRepositoryException 215 { 216 if (workflowLimitations.isEmpty()) 217 { 218 return true; 219 } 220 221 AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content); 222 223 List<Step> workflowCurrentSteps = workflow.getCurrentSteps(content.getWorkflowId()); 224 225 for (Step step : workflowCurrentSteps) 226 { 227 for (int stepId : workflowLimitations) 228 { 229 if (step.getStepId() == stepId) 230 { 231 return true; 232 } 233 } 234 } 235 236 // No match 237 return false; 238 } 239 240 /** 241 * Parses the attribute definition's restrictions. 242 * @param configuration the configuration of the element that have content restrictions 243 * @return the parsed restrictions. 244 * @throws ConfigurationException if the configuration is not valid. 245 */ 246 public Restrictions _parseRestrictions(Configuration configuration) throws ConfigurationException 247 { 248 Restrictions restrictions = new Restrictions(); 249 populateRestrictions(configuration, restrictions); 250 return restrictions; 251 } 252 253 /** 254 * Parses the attribute definition's restrictions. 255 * TODO NEWATTRIBUTEAPI_CONTENT: This method is public to be used in MetadataAndRepeaterDefinitionParser from {@link DefaultContentType}. Make this method private when it is deleted 256 * @param attributeConfiguration the attribute configuration to use. 257 * @param restrictions the restrictions. 258 * @throws ConfigurationException if the configuration is not valid. 259 */ 260 public void populateRestrictions(Configuration attributeConfiguration, Restrictions restrictions) throws ConfigurationException 261 { 262 Configuration restrictToConf = attributeConfiguration.getChild("restrict-to", true); 263 264 _populateNegativeRestrictions(restrictToConf, restrictions); 265 _populateRightRestrictions(restrictToConf, restrictions); 266 _populateWorkflowRestrictions(restrictToConf, restrictions); 267 } 268 269 /** 270 * Populates the negative restrictions. 271 * @param restrictionsConfig the restrictions configuration to use. 272 * @param restrictions the restrictions. 273 * @throws ConfigurationException if the configuration is not valid. 274 */ 275 private void _populateNegativeRestrictions(Configuration restrictionsConfig, Restrictions restrictions) throws ConfigurationException 276 { 277 for (Configuration noRightConfig : restrictionsConfig.getChildren("cannot")) 278 { 279 boolean isRead = _parseAccessType(noRightConfig); 280 281 if (isRead) 282 { 283 restrictions.setCannotRead(true); 284 } 285 else 286 { 287 restrictions.setCannotWrite(true); 288 } 289 } 290 } 291 292 /** 293 * Populates the rights restrictions. 294 * @param restrictionsConfig the restrictions configuration to use. 295 * @param restrictions the restrictions. 296 * @throws ConfigurationException if the configuration is not valid. 297 */ 298 private void _populateRightRestrictions(Configuration restrictionsConfig, Restrictions restrictions) throws ConfigurationException 299 { 300 for (Configuration rightConfig : restrictionsConfig.getChildren("right")) 301 { 302 String rightId = rightConfig.getAttribute("id", null); 303 304 if (rightId == null) 305 { 306 throw new ConfigurationException("Attribute 'id' is mandatory on 'right' element in a content type configuration.", rightConfig); 307 } 308 309 boolean isRead = _parseAccessType(rightConfig); 310 311 if (isRead) 312 { 313 restrictions.addReadRightIds(rightId); 314 } 315 else 316 { 317 restrictions.addWriteRightIds(rightId); 318 } 319 } 320 } 321 322 /** 323 * Populates the workflows restrictions. 324 * @param restrictionsConfig the restrictions configuration to use. 325 * @param restrictions the restrictions. 326 * @throws ConfigurationException if the configuration is not valid. 327 */ 328 private void _populateWorkflowRestrictions(Configuration restrictionsConfig, Restrictions restrictions) throws ConfigurationException 329 { 330 for (Configuration workflowConfig : restrictionsConfig.getChildren("workflow")) 331 { 332 String stepId = workflowConfig.getAttribute("step", null); 333 int stepIdValue = -1; 334 335 if (stepId != null) 336 { 337 try 338 { 339 stepIdValue = Integer.valueOf(stepId); 340 } 341 catch (NumberFormatException e) 342 { 343 // Handled just below 344 } 345 } 346 347 boolean isRead = _parseAccessType(workflowConfig); 348 349 if (isRead) 350 { 351 restrictions.addReadWorkflowfStepIds(stepIdValue); 352 } 353 else 354 { 355 restrictions.addWriteWorkflowfStepIds(stepIdValue); 356 } 357 } 358 } 359 360 /** 361 * Parses type attribute from a configuration. 362 * @param configuration the configuration. 363 * @return <code>true</code> for <code>read</code> type, 364 * <code>false</code> for <code>write</code> type. 365 * @throws ConfigurationException if the configuration is not valid. 366 */ 367 private boolean _parseAccessType(Configuration configuration) throws ConfigurationException 368 { 369 String type = configuration.getAttribute("read-write-direction"); 370 371 if ("read".equalsIgnoreCase(type)) 372 { 373 return true; 374 } 375 else if ("write".equalsIgnoreCase(type)) 376 { 377 return false; 378 } 379 else 380 { 381 throw new ConfigurationException("Attribute 'type' must be 'read' or 'write'.", configuration); 382 } 383 } 384}