001/* 002 * Copyright 2021 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.joboffer.right; 017 018import java.util.Collection; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.avalon.framework.service.Serviceable; 027import org.apache.cocoon.components.ContextHelper; 028import org.apache.commons.collections.MapUtils; 029import org.apache.commons.lang3.ArrayUtils; 030import org.apache.commons.lang3.StringUtils; 031 032import org.ametys.cms.contenttype.ContentTypesHelper; 033import org.ametys.cms.data.ContentValue; 034import org.ametys.cms.repository.Content; 035import org.ametys.cms.repository.ContentQueryHelper; 036import org.ametys.cms.repository.ContentTypeExpression; 037import org.ametys.core.group.GroupIdentity; 038import org.ametys.core.right.AccessController; 039import org.ametys.core.right.AccessExplanation; 040import org.ametys.core.right.RightsException; 041import org.ametys.core.user.UserIdentity; 042import org.ametys.plugins.core.impl.right.AbstractRightBasedAccessController; 043import org.ametys.plugins.joboffer.JobOfferConstants; 044import org.ametys.plugins.repository.AmetysObjectIterable; 045import org.ametys.plugins.repository.AmetysObjectResolver; 046import org.ametys.plugins.repository.query.expression.AndExpression; 047import org.ametys.plugins.repository.query.expression.Expression; 048import org.ametys.plugins.repository.query.expression.Expression.Operator; 049import org.ametys.plugins.repository.query.expression.OrExpression; 050import org.ametys.plugins.repository.query.expression.StringExpression; 051import org.ametys.plugins.repository.query.expression.UserExpression; 052import org.ametys.runtime.i18n.I18nizableText; 053import org.ametys.web.WebHelper; 054import org.ametys.web.repository.SiteAwareAmetysObject; 055 056/** 057 * {@link AccessController} so responsible of a job offer can access and handle the applications 058 * 059 */ 060public class ApplicationAccessController extends AbstractRightBasedAccessController implements Serviceable 061{ 062 /** ContentTypes Helper */ 063 protected ContentTypesHelper _cTypeHelper; 064 /** the ametys object resolver */ 065 protected AmetysObjectResolver _resolver; 066 067 public void service(ServiceManager smanager) throws ServiceException 068 { 069 _cTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE); 070 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 071 } 072 073 /** 074 * Get the rights for person in charge of a application content 075 * @return the list of allowed rights 076 */ 077 protected List<String> getApplicationRights() 078 { 079 return List.of( 080 "Workflow_Right_Application_Edit", 081 "Workflow_Right_Application_Shortlist", 082 "Workflow_Right_Application_Disapprove"); 083 } 084 085 /** 086 * Determines if the current user is in charge of the current application 087 * @param user the user 088 * @param content the application content 089 * @return true if the current user is in charge 090 */ 091 protected boolean isInCharge(UserIdentity user, Content content) 092 { 093 UserIdentity[] personsInCharge = getPersonInCharge(content); 094 return personsInCharge != null && ArrayUtils.contains(personsInCharge, user); 095 } 096 097 /** 098 * Get the persons in charge of a application 099 * @param content the application content 100 * @return the persons in charge or null if not found or empty 101 */ 102 protected UserIdentity[] getPersonInCharge(Content content) 103 { 104 if (content.hasDefinition(JobOfferConstants.JOB_APPLICATION_ATTRIBUTE_PATH_PERSON_IN_CHARGE)) 105 { 106 return content.getValue(JobOfferConstants.JOB_APPLICATION_ATTRIBUTE_PATH_PERSON_IN_CHARGE); 107 } 108 109 return null; 110 } 111 112 public boolean isSupported(Object object) 113 { 114 return object instanceof Content && _cTypeHelper.isInstanceOf((Content) object, JobOfferConstants.JOB_APPLICATION_CONTENT_TYPE); 115 } 116 117 public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object) 118 { 119 if (object instanceof Content && isInCharge(user, (Content) object)) 120 { 121 return getApplicationRights().contains(rightId) ? AccessResult.USER_ALLOWED : AccessResult.UNKNOWN; 122 } 123 124 return AccessResult.UNKNOWN; 125 } 126 127 public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 128 { 129 if (object instanceof Content && isInCharge(user, (Content) object)) 130 { 131 return AccessResult.USER_ALLOWED; 132 } 133 134 return AccessResult.UNKNOWN; 135 } 136 137 /** 138 * If creator, access to a list of rights 139 */ 140 public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 141 { 142 Map<String, AccessResult> permissionByRight = new HashMap<>(); 143 144 if (isInCharge(user, (Content) object)) 145 { 146 for (String rightId : getApplicationRights()) 147 { 148 permissionByRight.put(rightId, AccessResult.USER_ALLOWED); 149 } 150 } 151 152 return permissionByRight; 153 } 154 155 public AccessResult getPermissionForAnonymous(String rightId, Object object) 156 { 157 return AccessResult.UNKNOWN; 158 } 159 160 public AccessResult getReadAccessPermissionForAnonymous(Object object) 161 { 162 return AccessResult.UNKNOWN; 163 } 164 165 public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object) 166 { 167 return AccessResult.UNKNOWN; 168 } 169 170 public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object) 171 { 172 return AccessResult.UNKNOWN; 173 } 174 175 /** 176 * If right requested is in the list, the creator is added the list of USER_ALLOWED 177 */ 178 public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object) 179 { 180 Map<UserIdentity, AccessResult> permissionByUser = new HashMap<>(); 181 182 if (getApplicationRights().contains(rightId)) 183 { 184 UserIdentity[] personInCharge = getPersonInCharge((Content) object); 185 if (personInCharge != null) 186 { 187 for (UserIdentity userIdentity : personInCharge) 188 { 189 permissionByUser.put(userIdentity, AccessResult.USER_ALLOWED); 190 } 191 } 192 } 193 194 return permissionByUser; 195 } 196 197 public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object) 198 { 199 Map<UserIdentity, AccessResult> readPermissionByUser = new HashMap<>(); 200 201 UserIdentity[] personInCharge = getPersonInCharge((Content) object); 202 if (personInCharge != null) 203 { 204 for (UserIdentity userIdentity : personInCharge) 205 { 206 readPermissionByUser.put(userIdentity, AccessResult.USER_ALLOWED); 207 } 208 } 209 210 return readPermissionByUser; 211 } 212 213 public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object) 214 { 215 return MapUtils.EMPTY_MAP; 216 } 217 218 public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object) 219 { 220 return MapUtils.EMPTY_MAP; 221 } 222 223 public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId) 224 { 225 return false; 226 } 227 228 public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups) 229 { 230 return false; 231 } 232 233 public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 234 { 235 return false; 236 } 237 238 public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 239 { 240 return false; 241 } 242 243 public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 244 { 245 return false; 246 } 247 248 public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 249 { 250 return false; 251 } 252 253 @Override 254 public AccessExplanation getStandardAccessExplanation(AccessResult permission, Object object) 255 { 256 switch (permission) 257 { 258 case USER_ALLOWED: 259 case UNKNOWN: 260 Content jobApplication = (Content) object; 261 ContentValue jobOffer = jobApplication.getValue(JobOfferConstants.JOB_APPLICATION_ATTRIBUTE_PATH_JOB_OFFER); 262 return new AccessExplanation( 263 getId(), 264 permission, 265 new I18nizableText("plugin.job-offer", "PLUGINS_JOB_OFFER_APPLICATION_ACCESS_CONTROLLER_" + permission.name() + "_EXPLANATION", 266 Map.of( 267 "title", new I18nizableText(jobOffer.getContent().getTitle()) 268 ) 269 ) 270 ); 271 default: 272 return AccessController.getDefaultAccessExplanation(getId(), permission); 273 } 274 } 275 276 @Override 277 protected Iterable<? extends Object> getHandledObjects(UserIdentity identity, Set<GroupIdentity> groups) 278 { 279 String siteName = WebHelper.getSiteName(ContextHelper.getRequest(_context)); 280 281 if (StringUtils.isNotBlank(siteName)) 282 { 283 Expression typeExpression = new ContentTypeExpression(Operator.EQ, JobOfferConstants.JOB_OFFER_CONTENT_TYPE); 284 Expression inChargeExpression = new UserExpression(JobOfferConstants.JOB_OFFER_ATTRIBUTE_PATH_PERSON_IN_CHARGE, Operator.EQ, identity, true); 285 Expression siteExpression = new StringExpression(SiteAwareAmetysObject.METADATA_SITE, Operator.EQ, siteName); 286 String query = ContentQueryHelper.getContentXPathQuery(new AndExpression(typeExpression, inChargeExpression, siteExpression)); 287 288 try (AmetysObjectIterable<Content> offers = _resolver.query(query)) 289 { 290 if (offers.getSize() > 0) 291 { 292 List<Expression> applicationsExpression = offers.stream() 293 .map(Content::getId) 294 .<Expression>map(id -> new StringExpression(JobOfferConstants.JOB_APPLICATION_ATTRIBUTE_PATH_JOB_OFFER, Operator.EQ, id)) 295 .toList(); 296 297 String applicationQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression( 298 new ContentTypeExpression(Operator.EQ, JobOfferConstants.JOB_APPLICATION_CONTENT_TYPE), 299 new OrExpression(applicationsExpression) 300 )); 301 302 return _resolver.query(applicationQuery); 303 } 304 } 305 } 306 return List.of(); 307 } 308 309 @Override 310 protected Collection<String> getHandledRights(UserIdentity identity, Set<GroupIdentity> groups, Object object) 311 { 312 return getApplicationRights(); 313 } 314 315 @Override 316 public I18nizableText getObjectCategory(Object object) 317 { 318 return new I18nizableText("plugin.job-offer", "PLUGINS_JOB_OFFER_APPLICATION_ACCESS_CONTROLLER_CONTEXT_CATEGORY"); 319 } 320 321 @Override 322 public I18nizableText getObjectLabel(Object object) 323 { 324 if (object instanceof Content application) 325 { 326 ContentValue jobOffer = application.getValue(JobOfferConstants.JOB_APPLICATION_ATTRIBUTE_PATH_JOB_OFFER); 327 return new I18nizableText(jobOffer.getContent().getTitle() + " > " + application.getTitle()); 328 } 329 throw new RightsException("Unsupported object: " + object.toString()); 330 } 331}