001/* 002 * Copyright 2025 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.odf.rights; 017 018import java.util.Collections; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023 024import org.apache.avalon.framework.configuration.Configurable; 025import org.apache.avalon.framework.configuration.Configuration; 026import org.apache.avalon.framework.configuration.ConfigurationException; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030 031import org.ametys.cms.contenttype.ContentType; 032import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 033import org.ametys.cms.contenttype.ContentTypesHelper; 034import org.ametys.cms.repository.Content; 035import org.ametys.cms.rights.ContentAccessController; 036import org.ametys.core.group.GroupIdentity; 037import org.ametys.core.right.AccessController; 038import org.ametys.core.right.AccessController.Permission.PermissionType; 039import org.ametys.core.right.AccessExplanation; 040import org.ametys.core.right.RightManager; 041import org.ametys.core.right.RightManager.RightResult; 042import org.ametys.core.right.RightsException; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.runtime.i18n.I18nizableText; 045import org.ametys.runtime.plugin.component.PluginAware; 046 047/** 048 * Access controller for odf content that don't have an edition right, the edition right will be checked based on the creation right (example : skills). 049 * 050 * <pre> 051 * Configuration needed : 052 * - 'content-type' : The content type 053 * -> The creation right will be retrieved from the content type definition 054 * - 'i18n-keys-prefix' : The prefix of the i18n keys that will be used : 055 * -> <b>i18n-keys-prefix</b>_USER_ALLOWED_EXPLANATION 056 * -> <b>i18n-keys-prefix</b>_ANY_CONNECTED_ALLOWED_EXPLANATION 057 * -> <b>i18n-keys-prefix</b>_UNKNOWN_EXPLANATION 058 * -> <b>i18n-keys-prefix</b>_CONTENT_EXPLANATION_LABEL 059 * -> <b>i18n-keys-prefix</b>_ALL_CONTENTS_EXPLANATION_LABEL 060 * -> <b>i18n-keys-prefix</b>_ALL_CONTENTS_LABEL 061 * </pre> 062 */ 063public class EditContentWithCreationRightAccessController implements AccessController, Serviceable, Configurable, PluginAware 064{ 065 /** Fake context to check on all contents */ 066 private static final String __ALL_CONTENTS_FAKE_CONTEXT = "$allContents$"; 067 068 private static List<String> _RIGHT_IDS = List.of("Workflow_Rights_Edition_Online"); 069 070 /** The contenttype to consider */ 071 protected String _contentType; 072 /** The creation right found in the contenttype */ 073 protected String _creationRight; 074 /** The prefix of the i18n keys to use to explain rights */ 075 protected String _i18nKeysPrefix; 076 077 /** The content type extension point */ 078 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 079 /** The content types helper */ 080 protected ContentTypesHelper _contentTypesHelper; 081 /** The right manager */ 082 protected RightManager _rightManager; 083 084 private String _pluginName; 085 private String _id; 086 087 public void service(ServiceManager manager) throws ServiceException 088 { 089 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 090 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 091 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 092 } 093 094 public void configure(Configuration configuration) throws ConfigurationException 095 { 096 _contentType = configuration.getChild("content-type").getValue(); 097 if (!_contentTypeExtensionPoint.hasExtension(_contentType)) 098 { 099 throw new ConfigurationException("The content type '" + _contentType + "' is not registered in the content type extension point."); 100 } 101 else 102 { 103 ContentType contentType = _contentTypeExtensionPoint.getExtension(_contentType); 104 _creationRight = contentType.getRight(); 105 106 if (_creationRight == null) 107 { 108 throw new ConfigurationException("The content type '" + _contentType + "' has not creation right in its definition."); 109 } 110 } 111 112 113 _i18nKeysPrefix = configuration.getChild("i18n-keys-prefix").getValue(); 114 } 115 116 public boolean supports(Object object) 117 { 118 return object instanceof Content content && _contentTypesHelper.isInstanceOf(content, _contentType); 119 } 120 121 public void setPluginInfo(String pluginName, String featureName, String id) 122 { 123 _pluginName = pluginName; 124 _id = id; 125 } 126 127 public AccessResult getPermission(UserIdentity user, Set<GroupIdentity> userGroups, String rightId, Object object) 128 { 129 if (_RIGHT_IDS.contains(rightId)) 130 { 131 return _rightManager.hasRight(user, _creationRight, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW ? AccessResult.USER_ALLOWED : AccessResult.UNKNOWN; 132 } 133 134 return AccessResult.UNKNOWN; 135 } 136 137 public Map<String, AccessResult> getPermissionByRight(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 138 { 139 Map<String, AccessResult> permissionByRight = new HashMap<>(); 140 141 if (_rightManager.hasRight(user, _creationRight, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW) 142 { 143 _RIGHT_IDS.stream() 144 .forEach(r -> { 145 permissionByRight.put(r, AccessResult.USER_ALLOWED); 146 }); 147 } 148 149 return permissionByRight; 150 } 151 152 public Map<UserIdentity, AccessResult> getPermissionByUser(String rightId, Object object) 153 { 154 Map<UserIdentity, AccessResult> permissionByUser = new HashMap<>(); 155 156 if (_RIGHT_IDS.contains(rightId)) 157 { 158 for (UserIdentity user : _rightManager.getAllowedUsers(_creationRight, "/${WorkspaceName}").getAllowedUsers()) 159 { 160 permissionByUser.put(user, AccessResult.USER_ALLOWED); 161 } 162 } 163 return permissionByUser; 164 } 165 166 public AccessExplanation explainReadAccessPermissionForAnyConnectedUser(Object object) 167 { 168 return _getAccessExplanation(getReadAccessPermissionForAnyConnectedUser(object), object); 169 } 170 171 public AccessExplanation explainReadAccessPermission(UserIdentity user, Set<GroupIdentity> groups, Object object) 172 { 173 return _getAccessExplanation(getReadAccessPermission(user, groups, object), object); 174 } 175 176 public AccessExplanation explainPermission(UserIdentity user, Set<GroupIdentity> groups, String rightId, Object object) 177 { 178 return _getAccessExplanation(getPermission(user, groups, rightId, object), object); 179 } 180 181 private AccessExplanation _getAccessExplanation(AccessResult permission, Object object) 182 { 183 switch (permission) 184 { 185 case USER_ALLOWED: 186 case ANY_CONNECTED_ALLOWED: 187 case UNKNOWN: 188 return new AccessExplanation(getId(), permission, 189 new I18nizableText("plugin." + _pluginName, _i18nKeysPrefix + "_" + permission.name() + "_EXPLANATION", 190 Map.of( 191 "objectLabel", getObjectLabelForExplanation(object) 192 ) 193 ) 194 ); 195 default: 196 return AccessController.getDefaultAccessExplanation(getId(), permission); 197 } 198 } 199 200 private I18nizableText getObjectLabelForExplanation(Object object) 201 { 202 if (object.equals(__ALL_CONTENTS_FAKE_CONTEXT)) 203 { 204 return new I18nizableText("plugin." + _pluginName, _i18nKeysPrefix + "_ALL_CONTENTS_EXPLANATION_LABEL"); 205 } 206 else if (object instanceof Content content) 207 { 208 return new I18nizableText( 209 "plugin." + _pluginName, 210 _i18nKeysPrefix + "_CONTENT_EXPLANATION_LABEL", 211 Map.of("title", new I18nizableText(content.getTitle())) 212 ); 213 } 214 throw new RightsException("Unsupported context: " + object.toString()); 215 } 216 217 @Override 218 public I18nizableText getObjectLabel(Object object) 219 { 220 if (object.equals(__ALL_CONTENTS_FAKE_CONTEXT)) 221 { 222 return new I18nizableText("plugin." + _pluginName, _i18nKeysPrefix + "_ALL_CONTENTS_LABEL"); 223 } 224 return new I18nizableText(((Content) object).getTitle()); 225 } 226 227 @Override 228 public Map<ExplanationObject, Map<Permission, AccessExplanation>> explainAllPermissions(UserIdentity identity, Set<GroupIdentity> groups, Set<Object> workspacesContexts) 229 { 230 // Do not list all the contents. 231 // Instead take advantage of the fact that the getPermission methods 232 // ignore the context to call them with a fake context to generate a explanation for "all the content of the type" 233 Map<Permission, AccessExplanation> contextPermissions = new HashMap<>(); 234 for (String rightId: _RIGHT_IDS) 235 { 236 AccessExplanation explanation = explainPermission(identity, groups, rightId, __ALL_CONTENTS_FAKE_CONTEXT); 237 if (explanation.accessResult() != AccessResult.UNKNOWN) 238 { 239 contextPermissions.put(new Permission(PermissionType.RIGHT, rightId), explanation); 240 } 241 } 242 243 return Map.of(getExplanationObject(__ALL_CONTENTS_FAKE_CONTEXT), contextPermissions); 244 } 245 246 public Map<Permission, AccessExplanation> explainAllPermissionsForAnyConnected(Object object) 247 { 248 return Collections.EMPTY_MAP; 249 } 250 251 public Map<UserIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByUser(Object object) 252 { 253 Map<UserIdentity, Map<Permission, AccessExplanation>> permissionsByUser = new HashMap<>(); 254 255 // All user have the same set of permission. Compute it once and for all 256 Map<Permission, AccessExplanation> userPermissions = new HashMap<>(); 257 AccessExplanation explanation = _getAccessExplanation(AccessResult.USER_ALLOWED, object); 258 for (String right: _RIGHT_IDS) 259 { 260 Permission permission = new Permission(PermissionType.RIGHT, right); 261 userPermissions.put(permission, explanation); 262 } 263 264 for (UserIdentity user : _rightManager.getAllowedUsers(_creationRight, "/${WorkspaceName}").getAllowedUsers()) 265 { 266 permissionsByUser.put(user, userPermissions); 267 } 268 269 return permissionsByUser; 270 } 271 272 @Override 273 public I18nizableText getObjectCategory(Object object) 274 { 275 return ContentAccessController.CONTENT_CONTEXT_CATEGORY; 276 } 277 278 public int getObjectPriority(Object object) 279 { 280 if (object.equals(__ALL_CONTENTS_FAKE_CONTEXT)) 281 { 282 return 5; 283 } 284 return AccessController.super.getObjectPriority(object); 285 } 286 287 public String getId() 288 { 289 return _id; 290 } 291 292 public AccessResult getReadAccessPermission(UserIdentity user, Set<GroupIdentity> userGroups, Object object) 293 { 294 return AccessResult.UNKNOWN; 295 } 296 297 public AccessResult getReadAccessPermissionForAnonymous(Object object) 298 { 299 return AccessResult.UNKNOWN; 300 } 301 302 public AccessResult getReadAccessPermissionForAnyConnectedUser(Object object) 303 { 304 return AccessResult.UNKNOWN; 305 } 306 307 public Map<UserIdentity, AccessResult> getReadAccessPermissionByUser(Object object) 308 { 309 return Collections.EMPTY_MAP; 310 } 311 312 public Map<GroupIdentity, AccessResult> getReadAccessPermissionByGroup(Object object) 313 { 314 return Collections.EMPTY_MAP; 315 } 316 317 public boolean hasUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups) 318 { 319 return false; 320 } 321 322 public boolean hasAnonymousAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 323 { 324 return false; 325 } 326 327 public boolean hasAnyConnectedUserAnyReadAccessPermissionOnWorkspace(Set<Object> workspacesContexts) 328 { 329 return false; 330 } 331 332 public AccessResult getPermissionForAnyConnectedUser(String rightId, Object object) 333 { 334 return AccessResult.UNKNOWN; 335 } 336 337 public AccessResult getPermissionForAnonymous(String rightId, Object object) 338 { 339 return AccessResult.UNKNOWN; 340 } 341 342 public Map<GroupIdentity, AccessResult> getPermissionByGroup(String rightId, Object object) 343 { 344 return Collections.EMPTY_MAP; 345 } 346 347 public boolean hasUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, UserIdentity user, Set<GroupIdentity> userGroups, String rightId) 348 { 349 return false; 350 } 351 352 public boolean hasAnonymousAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 353 { 354 return false; 355 } 356 357 public boolean hasAnyConnectedUserAnyPermissionOnWorkspace(Set<Object> workspacesContexts, String rightId) 358 { 359 return false; 360 } 361 362 public Map<Permission, AccessExplanation> explainAllPermissionsForAnonymous(Object object) 363 { 364 return Map.of(); 365 } 366 367 public Map<GroupIdentity, Map<Permission, AccessExplanation>> explainAllPermissionsByGroup(Object object) 368 { 369 return Map.of(); 370 } 371}