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.HashSet; 019import java.util.List; 020import java.util.Objects; 021import java.util.Set; 022import java.util.stream.Collectors; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026 027import org.ametys.cms.content.ContentHelper; 028import org.ametys.cms.repository.Content; 029import org.ametys.core.right.AccessController; 030import org.ametys.core.right.RightsException; 031import org.ametys.odf.ODFHelper; 032import org.ametys.odf.ProgramItem; 033import org.ametys.odf.data.EducationalPath; 034import org.ametys.odf.orgunit.OrgUnit; 035import org.ametys.odf.rights.ODFRightHelper.ContextualizedContent; 036import org.ametys.odf.rights.ODFRightHelper.ContextualizedPermissionContext; 037import org.ametys.odf.tree.ODFContentsTreeHelper; 038import org.ametys.plugins.core.impl.right.AbstractHierarchicalWithPermissionContextAccessController; 039import org.ametys.plugins.repository.AmetysObjectResolver; 040import org.ametys.runtime.i18n.I18nizableText; 041 042/** 043 * {@link AccessController} for a ODF {@link ContextualizedContent} based on stored ACL 044 */ 045public class ODFContextualizedContentHierarchicalAccessController extends AbstractHierarchicalWithPermissionContextAccessController<ContextualizedContent, ODFRightHelper.ContextualizedPermissionContext> 046{ 047 /** the odf context category */ 048 public static final I18nizableText ODF_CONTEXT_CATEGORY = new I18nizableText("plugin.odf", "PLUGINS_ODF_RIGHT_ASSIGNMENT_CONTEXT_CONTENTS_LABEL"); 049 /** The helper for root content */ 050 protected ODFHelper _odfHelper; 051 /** The helper for contents */ 052 protected ContentHelper _contentHelper; 053 /** Ametys Object Resolver */ 054 protected AmetysObjectResolver _resolver; 055 /** The right helper for ODF contents */ 056 protected ODFRightHelper _odfRightHelper; 057 /** The ODF contents tree helper */ 058 protected ODFContentsTreeHelper _odfContentTreeHelper; 059 060 @Override 061 public void service(ServiceManager manager) throws ServiceException 062 { 063 super.service(manager); 064 _odfContentTreeHelper = (ODFContentsTreeHelper) manager.lookup(ODFContentsTreeHelper.ROLE); 065 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 066 _odfRightHelper = (ODFRightHelper) manager.lookup(ODFRightHelper.ROLE); 067 _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE); 068 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 069 } 070 071 public boolean supports(Object object) 072 { 073 return object instanceof ContextualizedContent; 074 } 075 076 @Override 077 protected Set<ContextualizedContent> _getParents(ContextualizedContent object, ContextualizedPermissionContext permissionCtx) 078 { 079 Content content = object.content(); 080 081 if (content instanceof ProgramItem programItem) 082 { 083 EducationalPath educationalPath = permissionCtx.getEducationalPath(); 084 if (educationalPath == null) 085 { 086 return Set.of(); 087 } 088 089 List<ProgramItem> programItemsInPath = educationalPath.getProgramItems(_resolver); 090 091 ProgramItem parent = programItemsInPath.getLast(); 092 093 // Update educational path in permission context 094 permissionCtx.withEducationalPath(_removeLast(educationalPath)); 095 096 Set<ContextualizedContent> parents = new HashSet<>(); 097 parents.add(new ContextualizedContent((Content) parent, permissionCtx.getEducationalPath())); 098 099 // Add orgunits 100 List<String> ouIds = programItem.getOrgUnits(); 101 parents.addAll(ouIds.stream() 102 .filter(Objects::nonNull) 103 .filter(_resolver::hasAmetysObjectForId) 104 .map(_resolver::resolveById) 105 .map(OrgUnit.class::cast) 106 .map(ou -> new ContextualizedContent(ou, null)) 107 .collect(Collectors.toSet())); 108 109 return Set.of(new ContextualizedContent((Content) parent, permissionCtx.getEducationalPath())); 110 } 111 else if (content instanceof OrgUnit ou) 112 { 113 OrgUnit parentOrgUnit = ou.getParentOrgUnit(); 114 if (parentOrgUnit != null) 115 { 116 return Set.of(new ContextualizedContent(parentOrgUnit, null)); 117 } 118 } 119 120 return Set.of(); 121 } 122 123 @Override 124 protected ContextualizedPermissionContext _getPermissionContext(ContextualizedContent contextualizedContent) 125 { 126 EducationalPath educationalPath = contextualizedContent.path(); 127 Content initialContent = contextualizedContent.content(); 128 129 // Remove initial content from education path 130 List<ProgramItem> programItemsInPath = educationalPath.getProgramItems(_resolver); 131 if (programItemsInPath.getLast().getId().equals(initialContent.getId())) 132 { 133 educationalPath = _removeLast(educationalPath); 134 } 135 return new ContextualizedPermissionContext(initialContent, educationalPath); 136 } 137 138 private EducationalPath _removeLast(EducationalPath educationalPath) 139 { 140 List<ProgramItem> programItemsInPath = educationalPath.getProgramItems(_resolver); 141 List<ProgramItem> subList = programItemsInPath.subList(0, programItemsInPath.size() - 1); 142 if (!subList.isEmpty()) 143 { 144 return EducationalPath.of(subList.toArray(ProgramItem[]::new)); 145 } 146 return null; 147 } 148 149 @Override 150 protected Object _convertContext(Object initialContext) 151 { 152 if (initialContext instanceof ContextualizedContent contextualizedContent) 153 { 154 return contextualizedContent.content(); 155 } 156 157 return super._convertContext(initialContext); 158 } 159 160 @Override 161 protected Set< ? extends Object> _convertWorkspaceToRootRightContexts(Set<Object> workspacesContexts) 162 { 163 return null; 164 } 165 166 public I18nizableText getObjectLabel(Object object) 167 { 168 if (object instanceof ContextualizedContent contextualizedContent) 169 { 170 return ODFContentHierarchicalAccessController.getContentObjectLabel(contextualizedContent.content(), _odfContentTreeHelper); 171 } 172 throw new RightsException("Unsupported context: " + object.toString()); 173 } 174 175 public I18nizableText getObjectCategory(Object object) 176 { 177 return ODFContentHierarchicalAccessController.ODF_CONTEXT_CATEGORY; 178 } 179}