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