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