001/* 002 * Copyright 2016 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.core.authentication; 017 018import java.lang.reflect.InvocationTargetException; 019import java.util.HashMap; 020import java.util.LinkedHashMap; 021import java.util.Map; 022import java.util.Set; 023 024import org.apache.avalon.framework.activity.Disposable; 025import org.apache.avalon.framework.activity.Initializable; 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.configuration.Configuration; 028import org.apache.avalon.framework.configuration.ConfigurationException; 029import org.apache.avalon.framework.context.Context; 030import org.apache.avalon.framework.context.ContextException; 031import org.apache.avalon.framework.context.Contextualizable; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.avalon.framework.thread.ThreadSafe; 036import org.apache.cocoon.components.LifecycleHelper; 037import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041import org.ametys.core.model.type.ModelItemTypeExtensionPoint; 042import org.ametys.runtime.i18n.I18nizableText; 043import org.ametys.runtime.model.ElementDefinition; 044import org.ametys.runtime.model.ElementDefinitionParser; 045import org.ametys.runtime.model.Enumerator; 046import org.ametys.runtime.model.checker.ItemChecker; 047import org.ametys.runtime.model.checker.ItemCheckerDescriptor; 048import org.ametys.runtime.model.checker.ItemCheckerParser; 049import org.ametys.runtime.parameter.Validator; 050import org.ametys.runtime.plugin.ExtensionPoint; 051import org.ametys.runtime.plugin.component.AbstractLogEnabled; 052import org.ametys.runtime.plugin.component.LogEnabled; 053import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 054 055/** 056 * This extension point handles a list of {@link CredentialProvider} handled by the plugins. 057 */ 058public class CredentialProviderFactory extends AbstractLogEnabled implements ExtensionPoint<CredentialProviderModel>, Initializable, ThreadSafe, Component, Serviceable, Contextualizable, Disposable 059{ 060 /** The avalon role */ 061 public static final String ROLE = CredentialProviderFactory.class.getName(); 062 /** The Credential Provider Parameter Extension Point */ 063 protected ModelItemTypeExtensionPoint _credentialProviderParameterTypeExtensionPoint; 064 065 private Map<String, CredentialProviderModel> _cpModels; 066 067 private ServiceManager _smanager; 068 069 private Context _context; 070 071 @Override 072 public void initialize() throws Exception 073 { 074 _cpModels = new HashMap<>(); 075 } 076 077 @Override 078 public void dispose() 079 { 080 _cpModels.clear(); 081 } 082 083 @Override 084 public void service(ServiceManager smanager) throws ServiceException 085 { 086 _smanager = smanager; 087 _credentialProviderParameterTypeExtensionPoint = (ModelItemTypeExtensionPoint) _smanager.lookup(ModelItemTypeExtensionPoint.ROLE_CREDENTIAL_PROVIDER); 088 } 089 090 @Override 091 public void contextualize(Context context) throws ContextException 092 { 093 _context = context; 094 } 095 096 /** 097 * Creates a instance of {@link CredentialProvider} 098 * @param id The unique id of this credential provider instance 099 * @param modelId The id of the credential provider model 100 * @param paramsValues the parameters's values 101 * @param label The optional label 102 * @return a credential provider 103 */ 104 public CredentialProvider createCredentialProvider (String id, String modelId, Map<String, Object> paramsValues, String label) 105 { 106 if (_cpModels.containsKey(modelId)) 107 { 108 CredentialProviderModel credentialProviderModel = _cpModels.get(modelId); 109 110 CredentialProvider cp = null; 111 Class<CredentialProvider> cpClass = credentialProviderModel.getCredentialProviderClass(); 112 113 try 114 { 115 cp = credentialProviderModel.getCredentialProviderClass().getDeclaredConstructor().newInstance(); 116 } 117 catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) 118 { 119 throw new IllegalArgumentException("Cannot instanciate the class " + cpClass.getCanonicalName() + ". Check that there is a public constructor with no arguments."); 120 } 121 122 Logger logger = LoggerFactory.getLogger(cpClass); 123 try 124 { 125 if (cp instanceof LogEnabled) 126 { 127 ((LogEnabled) cp).setLogger(logger); 128 } 129 130 LifecycleHelper.setupComponent(cp, new SLF4JLoggerAdapter(logger), _context, _smanager, credentialProviderModel.getCredentialProviderConfiguration()); 131 132 cp.init(id, modelId, paramsValues, label); 133 } 134 catch (Exception e) 135 { 136 getLogger().error("An error occured during the initialization of the CredentialProvider " + id + " [" + modelId + "]", e); 137 return null; 138 } 139 140 return cp; 141 } 142 143 return null; 144 } 145 146 @Override 147 public void addExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException 148 { 149 if (getLogger().isDebugEnabled()) 150 { 151 getLogger().debug("Adding credential provider model from plugin " + pluginName + "/" + featureName); 152 } 153 154 try 155 { 156 addCredentialProviderModel(pluginName, configuration); 157 } 158 catch (ConfigurationException e) 159 { 160 if (getLogger().isWarnEnabled()) 161 { 162 getLogger().warn("The plugin '" + pluginName + "." + featureName + "' has a credential provider model extension but has an incorrect configuration", e); 163 } 164 } 165 } 166 167 /** 168 * Add a credential provider model 169 * @param pluginName The plugin name 170 * @param configuration The configuration 171 * @throws ConfigurationException when a configuration problem occurs 172 */ 173 protected void addCredentialProviderModel (String pluginName, Configuration configuration) throws ConfigurationException 174 { 175 String id = configuration.getAttribute("id"); 176 I18nizableText label = I18nizableText.parseI18nizableText(configuration.getChild("label"), "plugin." + pluginName); 177 I18nizableText description = I18nizableText.parseI18nizableText(configuration.getChild("description"), "plugin." + pluginName); 178 I18nizableText connectionLabel; 179 if (configuration.getChild("label-connection", false) == null) 180 { 181 connectionLabel = null; 182 } 183 else 184 { 185 connectionLabel = I18nizableText.parseI18nizableText(configuration.getChild("label-connection"), "plugin." + pluginName); 186 } 187 String iconGlyph = configuration.getChild("icon-glyph").getValue(""); 188 String iconDecorator = configuration.getChild("icon-decorator").getValue(""); 189 String iconSmallPath = configuration.getChild("icon-small").getValue(""); 190 String iconMediumPath = configuration.getChild("icon-medium").getValue(""); 191 String iconLargePath = configuration.getChild("icon-large").getValue(""); 192 String connectionColor = configuration.getChild("color").getValue(""); 193 194 String className = null; 195 Class<?> cpClass = null; 196 Configuration classConfig = null; 197 try 198 { 199 className = configuration.getChild("class").getAttribute("name"); 200 cpClass = Class.forName(className); 201 classConfig = configuration.getChild("class"); 202 } 203 catch (ClassNotFoundException | ConfigurationException e) 204 { 205 throw new ConfigurationException("Credential provider model with id '" + id + "' has an invalid configuration for class name '" + (className != null ? className + " <class not found>" : "<missing tag <class>") + "'", e); 206 } 207 208 if (!CredentialProvider.class.isAssignableFrom(cpClass)) 209 { 210 throw new ConfigurationException("Credential provider model with id '" + id + "' has an invalid configuration: '" + className + "' is not an instance of CredentialProvider"); 211 } 212 213 Map<String, ElementDefinition> parameters = new LinkedHashMap<>(); 214 215 ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>(); 216 validatorManager.setLogger(getLogger()); 217 validatorManager.contextualize(_context); 218 validatorManager.service(_smanager); 219 220 ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>(); 221 enumeratorManager.setLogger(getLogger()); 222 enumeratorManager.contextualize(_context); 223 enumeratorManager.service(_smanager); 224 225 CredentialProviderModelParameterParser cpParser = new CredentialProviderModelParameterParser(_credentialProviderParameterTypeExtensionPoint, enumeratorManager, validatorManager); 226 227 Configuration[] paramsConfiguration = configuration.getChild("parameters").getChildren("param"); 228 for (Configuration paramConfiguration : paramsConfiguration) 229 { 230 configureParameters(cpParser, paramConfiguration, pluginName, parameters); 231 } 232 233 // Parse parameter checkers 234 Map<String, ItemCheckerDescriptor> parameterCheckers = new LinkedHashMap<>(); 235 236 ThreadSafeComponentManager<ItemChecker> parameterCheckerManager = new ThreadSafeComponentManager<>(); 237 parameterCheckerManager.setLogger(getLogger()); 238 parameterCheckerManager.contextualize(_context); 239 parameterCheckerManager.service(_smanager); 240 241 ItemCheckerParser parameterCheckerParser = new ItemCheckerParser(parameterCheckerManager); 242 243 Configuration[] paramCheckersConfiguration = configuration.getChild("parameters").getChildren("param-checker"); 244 for (Configuration paramCheckerConfiguration : paramCheckersConfiguration) 245 { 246 configureParamChecker(parameterCheckerParser, paramCheckerConfiguration, pluginName, parameterCheckers); 247 } 248 249 try 250 { 251 cpParser.lookupComponents(); 252 parameterCheckerParser.lookupComponents(); 253 } 254 catch (Exception e) 255 { 256 throw new ConfigurationException("Unable to lookup parameter local components", configuration, e); 257 } 258 259 @SuppressWarnings("unchecked") 260 CredentialProviderModel cpModel = new DefaultCredentialProviderModel(id, (Class<CredentialProvider>) cpClass, classConfig, label, description, connectionLabel, iconGlyph, iconDecorator, iconSmallPath, iconMediumPath, iconLargePath, connectionColor, parameters, parameterCheckers, pluginName); 261 if (_cpModels.containsKey(id)) 262 { 263 CredentialProviderModel oldCPModel = _cpModels.get(id); 264 throw new IllegalArgumentException("Credential provider model with id '" + id + "' is already declared in plugin '" + oldCPModel.getPluginName() + "'. This second declaration is ignored."); 265 } 266 267 _cpModels.put(id, cpModel); 268 } 269 270 /** 271 * Configure a parameter to access the credential provider 272 * @param paramParser the parameter parser. 273 * @param configuration The parameter configuration. 274 * @param pluginName The plugin name 275 * @param parameters The model's parameters 276 * @throws ConfigurationException if configuration is incomplete or invalid. 277 */ 278 protected void configureParameters(CredentialProviderModelParameterParser paramParser, Configuration configuration, String pluginName, Map<String, ElementDefinition> parameters) throws ConfigurationException 279 { 280 ElementDefinition elementDefinition = paramParser.parse(_smanager, pluginName, configuration, null, null); 281 String id = elementDefinition.getName(); 282 283 if (parameters.containsKey(id)) 284 { 285 throw new ConfigurationException("The parameter '" + id + "' is already declared. IDs must be unique.", configuration); 286 } 287 288 parameters.put(id, elementDefinition); 289 } 290 291 /** 292 * Configure a parameter checker of a user directory 293 * @param parser the parameter checker parser. 294 * @param configuration The parameter checker configuration. 295 * @param pluginName The plugin name 296 * @param parameterCheckers The model's parameter checkers 297 * @throws ConfigurationException if configuration is incomplete or invalid. 298 */ 299 protected void configureParamChecker(ItemCheckerParser parser, Configuration configuration, String pluginName, Map<String, ItemCheckerDescriptor> parameterCheckers) throws ConfigurationException 300 { 301 ItemCheckerDescriptor parameterChecker = parser.parseParameterChecker(pluginName, configuration); 302 String id = parameterChecker.getName(); 303 304 if (parameterCheckers.containsKey(id)) 305 { 306 throw new ConfigurationException("The parameter checker '" + id + "' is already declared. IDs must be unique.", configuration); 307 } 308 309 parameterCheckers.put(id, parameterChecker); 310 } 311 312 @Override 313 public void initializeExtensions() throws Exception 314 { 315 // Nothing to do 316 } 317 318 @Override 319 public boolean hasExtension(String id) 320 { 321 return _cpModels.containsKey(id); 322 } 323 324 @Override 325 public CredentialProviderModel getExtension(String id) 326 { 327 return _cpModels.get(id); 328 } 329 330 @Override 331 public Set<String> getExtensionsIds() 332 { 333 return _cpModels.keySet(); 334 } 335 336 /** 337 * Class for parsing parameters of a {@link CredentialProviderModel} 338 */ 339 public class CredentialProviderModelParameterParser extends ElementDefinitionParser 340 { 341 /** 342 * Constructor 343 * @param credentialProviderParameterTypeExtensionPoint the extension point to use to get available element types 344 * @param enumeratorManager The manager for enumeration 345 * @param validatorManager The manager for validation 346 */ 347 public CredentialProviderModelParameterParser(ModelItemTypeExtensionPoint credentialProviderParameterTypeExtensionPoint, ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager) 348 { 349 super(credentialProviderParameterTypeExtensionPoint, enumeratorManager, validatorManager); 350 } 351 352 @Override 353 protected String _getNameConfigurationAttribute() 354 { 355 return "id"; 356 } 357 } 358}