001/* 002 * Copyright 2017 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.odfweb.restrictions; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.InputStream; 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027 028import org.apache.avalon.framework.activity.Disposable; 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.configuration.Configuration; 031import org.apache.avalon.framework.configuration.ConfigurationException; 032import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 033import org.apache.avalon.framework.context.Context; 034import org.apache.avalon.framework.context.ContextException; 035import org.apache.avalon.framework.context.Contextualizable; 036import org.apache.avalon.framework.service.ServiceException; 037import org.apache.avalon.framework.service.ServiceManager; 038import org.apache.avalon.framework.service.Serviceable; 039import org.apache.cocoon.Constants; 040import org.apache.cocoon.components.ContextHelper; 041import org.apache.cocoon.environment.Request; 042import org.apache.commons.lang3.StringUtils; 043import org.apache.commons.lang3.Strings; 044 045import org.ametys.cms.contenttype.ContentType; 046import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 047import org.ametys.cms.model.ContentElementDefinition; 048import org.ametys.core.ui.Callable; 049import org.ametys.odf.enumeration.OdfReferenceTableEntry; 050import org.ametys.odf.enumeration.OdfReferenceTableHelper; 051import org.ametys.odf.orgunit.OrgUnit; 052import org.ametys.odf.orgunit.RootOrgUnitProvider; 053import org.ametys.odf.program.ProgramFactory; 054import org.ametys.plugins.odfweb.restrictions.rules.OdfAndRestrictionRule; 055import org.ametys.plugins.odfweb.restrictions.rules.OdfAttributeRestrictionRule; 056import org.ametys.plugins.odfweb.restrictions.rules.OdfNotRestrictionRule; 057import org.ametys.plugins.odfweb.restrictions.rules.OdfOrRestrictionRule; 058import org.ametys.plugins.odfweb.restrictions.rules.OdfOrgunitRestrictionRule; 059import org.ametys.plugins.odfweb.restrictions.rules.OdfRestrictionRule; 060import org.ametys.plugins.repository.AmetysObjectResolver; 061import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector; 062import org.ametys.runtime.i18n.I18nizableText; 063import org.ametys.runtime.model.ModelItem; 064import org.ametys.runtime.plugin.component.AbstractLogEnabled; 065import org.ametys.web.repository.page.Page; 066import org.ametys.web.repository.site.Site; 067import org.ametys.web.repository.site.SiteManager; 068 069/** 070 * Component able to handle program restrictions related to the "odf-restrictions" site parameter. 071 */ 072public class OdfProgramRestrictionManager extends AbstractLogEnabled implements Component, Serviceable, Contextualizable, Disposable 073{ 074 /** The avalon role. */ 075 public static final String ROLE = OdfProgramRestrictionManager.class.getName(); 076 077 /** Site Manager */ 078 protected SiteManager _siteManager; 079 080 /** The content type extension point. */ 081 protected ContentTypeExtensionPoint _cTypeEP; 082 083 /** Ametys object resolver */ 084 protected AmetysObjectResolver _resolver; 085 086 /** The ODF reference table helper */ 087 protected OdfReferenceTableHelper _odfReferenceTableHelper; 088 089 /** Root orgunit provider */ 090 protected RootOrgUnitProvider _rootOrgUnitProvider; 091 092 /** Cocoon context */ 093 protected org.apache.cocoon.environment.Context _cocoonContext; 094 095 /** The available odf restrictions */ 096 protected Map<String, OdfProgramRestriction> _restrictions; 097 098 private Context _context; 099 100 @Override 101 public void service(ServiceManager serviceManager) throws ServiceException 102 { 103 _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE); 104 _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE); 105 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 106 _rootOrgUnitProvider = (RootOrgUnitProvider) serviceManager.lookup(RootOrgUnitProvider.ROLE); 107 _odfReferenceTableHelper = (OdfReferenceTableHelper) serviceManager.lookup(OdfReferenceTableHelper.ROLE); 108 } 109 110 @Override 111 public void contextualize(Context context) throws ContextException 112 { 113 _context = context; 114 _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 115 } 116 117 @Override 118 public void dispose() 119 { 120 _restrictions = null; 121 } 122 123 /** 124 * Retrieves the available restrictions 125 * @return The map of restriction, where keys are restriction ids. 126 */ 127 public Map<String, OdfProgramRestriction> getRestrictions() 128 { 129 // Lazy initialize restrictions because orgunit restrictions cannot be 130 // resolved during the initialization of the component 131 if (_restrictions == null) 132 { 133 _readRestrictionConfigurationFile(); 134 } 135 136 return _restrictions; 137 } 138 139 /** 140 * Get the ODF restriction for the given ODF root page 141 * @param odfRootPage The ODF root page 142 * @return The restriction or <code>null</code> 143 */ 144 public OdfProgramRestriction getRestriction(Page odfRootPage) 145 { 146 Site site = odfRootPage.getSite(); 147 String restrictionId = site.getValue("odf-restrictions"); 148 if (StringUtils.isNotEmpty(restrictionId)) 149 { 150 return getRestrictions().get(restrictionId); 151 } 152 153 return null; 154 } 155 156 /** 157 * Indicate is there is any restriction for this root page 158 * @param rootPage odf root page 159 * @return true if it is the case 160 */ 161 public boolean hasRestrictions(Page rootPage) 162 { 163 Site site = rootPage.getSite(); 164 String restrictionId = site.getValue("odf-restrictions"); 165 return StringUtils.isNotEmpty(restrictionId) ? getRestrictions().containsKey(restrictionId) : false; 166 } 167 168 /** 169 * Indicate is there is any restriction related to orgunit for this root page 170 * @param rootPage odf root page 171 * @return true if it is the case 172 */ 173 public boolean hasOrgunitRestrictions(Page rootPage) 174 { 175 Site site = rootPage.getSite(); 176 String restrictionId = site.getValue("odf-restrictions"); 177 if (StringUtils.isNotEmpty(restrictionId)) 178 { 179 OdfProgramRestriction odfProgramRestriction = getRestrictions().get(restrictionId); 180 return odfProgramRestriction.hasOrgunitRestrictions(); 181 } 182 183 return false; 184 } 185 186 /** 187 * Get the id of the restriction root orgunit 188 * @param siteName the site name 189 * @return the id of the restriction root orgunit 190 */ 191 @Callable(rights = Callable.NO_CHECK_REQUIRED) // only return content id without more info 192 public String getRestrictionRootOrgUnitId(String siteName) 193 { 194 String odfRootId = null; 195 196 Site site = _siteManager.getSite(siteName); 197 String restrictionId = site.getValue("odf-restrictions"); 198 if (StringUtils.isNotEmpty(restrictionId)) 199 { 200 OdfProgramRestriction odfProgramRestriction = getRestrictions().get(restrictionId); 201 if (odfProgramRestriction != null && odfProgramRestriction.hasOrgunitRestrictions()) 202 { 203 odfRootId = odfProgramRestriction.getId(); 204 } 205 } 206 207 if (StringUtils.isBlank(odfRootId)) 208 { 209 odfRootId = _rootOrgUnitProvider.getRootId(); 210 } 211 212 return odfRootId; 213 } 214 215 /** 216 * Configured the restrictions from the XML configuration file 217 */ 218 protected void _readRestrictionConfigurationFile() 219 { 220 _restrictions = new HashMap<>(); 221 222 File restrictionFile = new File (_cocoonContext.getRealPath("/WEB-INF/param/odf-restrictions.xml")); 223 224 if (restrictionFile.exists()) 225 { 226 try (InputStream is = new FileInputStream(restrictionFile);) 227 { 228 Configuration cfg = new DefaultConfigurationBuilder().build(is); 229 230 Configuration[] restrictionConfs = cfg.getChildren("restriction"); 231 for (Configuration restrictionConf : restrictionConfs) 232 { 233 String id = restrictionConf.getAttribute("id"); 234 I18nizableText label = _configureI18nizableText(restrictionConf, "label", "", "application"); 235 List<OdfRestrictionRule> rules = _configureRestrictionRules(restrictionConf.getChild("rules")); 236 237 _restrictions.put(id, new OdfProgramRestriction(id, label, rules)); 238 } 239 240 Configuration orgunitConf = cfg.getChild("orgunits", false); 241 if (orgunitConf != null) 242 { 243 _addDefaultOrgunitRestrictions(); 244 } 245 } 246 catch (Exception e) 247 { 248 getLogger().error("Cannot read the configuration file located at /WEB-INF/param/odf-restrictions.xml. Reverting to default.", e); 249 _restrictions.clear(); 250 _addDefaultOrgunitRestrictions(); 251 } 252 } 253 else 254 { 255 _addDefaultOrgunitRestrictions(); 256 } 257 } 258 259 private I18nizableText _configureI18nizableText(Configuration config, String name, String defaultValue, String defaultCatalog) 260 { 261 Configuration textConfig = config.getChild(name); 262 boolean i18nSupported = textConfig.getAttributeAsBoolean("i18n", false); 263 String text = textConfig.getValue(defaultValue); 264 265 if (i18nSupported) 266 { 267 String catalogue = textConfig.getAttribute("catalogue", defaultCatalog); 268 return new I18nizableText(catalogue, text); 269 } 270 else 271 { 272 return new I18nizableText(text); 273 } 274 } 275 276 /** 277 * Create a list of rules from configuration nodes 278 * @param rulesConf Array of configuration node that represents the set of rules 279 * @return list of rules 280 * @throws ConfigurationException if a configuration error is encountered 281 */ 282 protected List<OdfRestrictionRule> _configureRestrictionRules(Configuration rulesConf) throws ConfigurationException 283 { 284 List<OdfRestrictionRule> rules = new ArrayList<>(); 285 286 for (Configuration ruleConf : rulesConf.getChildren()) 287 { 288 rules.add(_configureRestrictionRule(ruleConf)); 289 } 290 291 return rules; 292 } 293 294 /** 295 * Configure a restriction rule from a configuration node 296 * @param ruleConf The configuration node representing the rule 297 * @return The odf restriction rule 298 * @throws ConfigurationException if a configuration error is encountered 299 */ 300 protected OdfRestrictionRule _configureRestrictionRule(Configuration ruleConf) throws ConfigurationException 301 { 302 String name = ruleConf.getName(); 303 304 if ("item".equals(name)) 305 { 306 String attributePath = ruleConf.getAttribute("ref"); 307 String attributeValue = ruleConf.getAttribute("value"); 308 309 ContentType programContentType = _cTypeEP.getExtension(ProgramFactory.PROGRAM_CONTENT_TYPE); 310 if (programContentType.hasModelItem(attributePath)) 311 { 312 ModelItem modelItem = programContentType.getModelItem(attributePath); 313 if (modelItem instanceof ContentElementDefinition def && _odfReferenceTableHelper.isTableReference(def.getContentTypeId())) 314 { 315 // If the attribute value is the table reference code, convert it and test the JCR id 316 OdfReferenceTableEntry itemFromCode = _odfReferenceTableHelper.getItemFromCode(def.getContentTypeId(), attributeValue); 317 if (itemFromCode != null) 318 { 319 return new OdfAttributeRestrictionRule(attributePath, itemFromCode.getId()); 320 } 321 } 322 323 return new OdfAttributeRestrictionRule(attributePath, attributeValue); 324 } 325 326 throw new ConfigurationException("Attribute path '" + attributePath + "' is unknown for program content type."); 327 } 328 else if ("orgunit".equals(name)) 329 { 330 String orgunitId = ruleConf.getAttribute("id", null); 331 332 if (StringUtils.isEmpty(orgunitId)) 333 { 334 throw new ConfigurationException("Expecting 'id' attribute for orgunit restriction rule."); 335 } 336 337 return new OdfOrgunitRestrictionRule(_rootOrgUnitProvider, orgunitId); 338 } 339 else if ("and".equals(name)) 340 { 341 List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf); 342 return new OdfAndRestrictionRule(childRules); 343 } 344 else if ("or".equals(name)) 345 { 346 List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf); 347 return new OdfOrRestrictionRule(childRules); 348 } 349 else if ("not".equals(name)) 350 { 351 List<OdfRestrictionRule> childRules = _configureRestrictionRules(ruleConf); 352 return new OdfNotRestrictionRule(childRules); 353 } 354 355 throw new ConfigurationException("Unknow node name in restriction configuration : " + name); 356 } 357 358 private void _addDefaultOrgunitRestrictions() 359 { 360 Request request = ContextHelper.getRequest(_context); 361 String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request); 362 363 try 364 { 365 // Force default workspace 366 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, null); 367 368 String rootOrgunitId = _rootOrgUnitProvider.getRootId(); 369 Set<String> orgunitIds = _rootOrgUnitProvider.getChildOrgUnitIds(rootOrgunitId, true); 370 orgunitIds.add(rootOrgunitId); 371 372 for (String id : orgunitIds) 373 { 374 if (!Strings.CS.equals(id, rootOrgunitId)) 375 { 376 OrgUnit orgunit = _resolver.resolveById(id); 377 378 OdfRestrictionRule rule = new OdfOrgunitRestrictionRule(_rootOrgUnitProvider, id); 379 OdfProgramRestriction restriction = new OdfProgramRestriction(id, new I18nizableText(orgunit.getTitle()), Collections.singletonList(rule)); 380 381 _restrictions.put(id, restriction); 382 } 383 } 384 } 385 finally 386 { 387 // Restore workspace 388 RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp); 389 } 390 } 391}