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