Author: bpoussin Date: 2012-01-09 20:03:18 +0100 (Mon, 09 Jan 2012) New Revision: 1311 Url: http://nuiton.org/repositories/revision/wikitty/1311 Log: Evolution #1890: Add external authentication like LDAP Added: trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurityExternalAuthentication.java trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurityExternalAuthenticationLDAP.java trunk/wikitty-api/src/test/java/org/nuiton/wikitty/services/WikittyServiceSecurityTest.java Modified: trunk/src/site/rst/user/security.rst trunk/wikitty-api/src/main/java/org/nuiton/wikitty/WikittyConfigOption.java trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurity.java trunk/wikitty-api/src/test/java/org/nuiton/wikitty/layers/WikittyServiceSecurityTest.java Modified: trunk/src/site/rst/user/security.rst =================================================================== --- trunk/src/site/rst/user/security.rst 2012-01-09 15:15:11 UTC (rev 1310) +++ trunk/src/site/rst/user/security.rst 2012-01-09 19:03:18 UTC (rev 1311) @@ -68,6 +68,52 @@ +--------------+ +--------------+ serveur client +Utiliser un moyen externe d'authentification +-------------------------------------------- + +Il est possible d'utiliser un moyen externe d'authentification, par exemple LDAP. +Pour cela il faut implanter la classe WikittyServiceSecurityExternalAuthentication +en implantant le mécanisme que l'on souhaite. Ensuite il faut l'ajouter a la +configuration pour que le service de security l'utilise:: + + wikitty.WikittyService.components=\ + org.nuiton.wikitty.WikittyServiceStorage,\ + // ...autres.services...,\ + org.nuiton.wikitty.services.WikittyServiceSecurity + wikitty.WikittyServiceSecurity.components=packages.MyExternalAuthentication + +Il faut bien vérifier que org.nuiton.wikitty.services.WikittyServiceSecurity est +déclaré dans la configuration sinon l'option seule +wikitty.WikittyServiceSecurity.components n'aura aucun effet. + + +Exemple d'utilisation de WikittyServiceSecurityExternalAuthenticationLDAP ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +Dans wikitty une implantation par défaut a été faite pour l'authentification LDAP +Voici un exemple de configuration:: + + wikitty.WikittyService.components=\ + org.nuiton.wikitty.WikittyServiceStorage,\ + // ...autres.services...,\ + org.nuiton.wikitty.services.WikittyServiceSecurity + wikitty.WikittyServiceSecurity.components=\ + org.nuiton.wikitty.services.WikittyServiceSecurityExternalAuthenticationLDAP + wikitty.security.externalAuthentication.ldap.server=ldap://intranet.codelutin.home:389 + wikitty.security.externalAuthentication.ldap.loginPattern=uid=%s,ou=People,dc=codelutin,dc=home + +Les deux options obligatoires sont l'url du serveur et le format des logins (DN) +le '%s' est remplacé par le login. + +Il est possible d'ajouter toutes les options acceptées par JNDI en les préfixant +par 'wikitty.security.externalAuthentication.ldap.jndi.' Par exemple on peut +modifier la factory par défaut avec:: + + wikitty.security.externalAuthentication.ldap.jndi.java.naming.factory.initial + +Voir la documentation JNDI pour toutes les options possibles + + Utilisateurs et groupes ----------------------- Modified: trunk/wikitty-api/src/main/java/org/nuiton/wikitty/WikittyConfigOption.java =================================================================== --- trunk/wikitty-api/src/main/java/org/nuiton/wikitty/WikittyConfigOption.java 2012-01-09 15:15:11 UTC (rev 1310) +++ trunk/wikitty-api/src/main/java/org/nuiton/wikitty/WikittyConfigOption.java 2012-01-09 19:03:18 UTC (rev 1311) @@ -5,7 +5,7 @@ * $Id$ * $HeadURL$ * %% - * Copyright (C) 2009 - 2011 CodeLutin + * Copyright (C) 2009 - 2012 CodeLutin, Poussin Benjamin * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -22,22 +22,6 @@ * <http://www.gnu.org/licenses/lgpl-3.0.html>. * #L% */ -/* - * Copyright (c) 2011 poussin. All rights reserved. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ package org.nuiton.wikitty; import static org.nuiton.i18n.I18n.n_; @@ -207,6 +191,51 @@ n_("Indique la liste d'extension dont il faut monitorer l'acces"), "WikittyPubText,WikittyPubData", String.class, false, false), + WIKITTY_WIKITTYSERVICESECURITY_COMPONENTS( + "wikitty.WikittyServiceSecurity.components", + n_("Indique le composant a utiliser pour l'authentification externe" + + " ex:WikittyServiceSecurityExternalAuthenticationLDAP"), + null, + String.class, false, false), + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_ONLY( + "wikitty.security.externalAuthenticationOnly", + n_("Indique si on doit utiliser que le composant d'authentification externe" + + " pour authentifier les utilisateurs. Dans ce cas le module doit" + + " etre configurer, sinon aucun utilisateur ne pourra se logguer"), + "false", + Boolean.class, false, false), + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_JNDI( + "wikitty.security.externalAuthentication.ldap.jndi.", + n_("Prefix a utiliser pour ajouter n'importe quelle option supporte par le" + + " jndi pour la creation du initialContext. Vous ajoutez derriere" + + " se prefixe la cle jndi ex: '<prefix>java.naming.security.protocol"), + null, + String.class, false, false), + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_JNDI_INITIAL_CONTEXT_FACTORY( + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_JNDI.getKey() + "java.naming.factory.initial", + n_("Indique l'initial context factory a utiliser par defaut 'com.sun.jndi.ldap.LdapCtxFactory'"), + "com.sun.jndi.ldap.LdapCtxFactory", + String.class, false, false), + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_JNDI_SECURITY_AUTHENTICATION( + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_JNDI.getKey() + "java.naming.security.authentication", + n_("Indique le mode d'authentification a utiliser par defaut 'simple'." + + " Les modes possibles sont 'simple', 'SSL', 'SASL'"), + "simple", + String.class, false, false), + // LDAP serveur n'est pas en prefix JNDI, car pourrait servir ailleur que pour JNDI + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_SERVER( + "wikitty.security.externalAuthentication.ldap.server", + n_("Indique l'url du serveur LDAP (obligatoire)"), + null, + String.class, false, false), + // LDAP login pattern n'est pas en prefix JNDI, car pourrait servir ailleur que pour JNDI + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_LOGIN_PATTERN( + "wikitty.security.externalAuthentication.ldap.loginPattern", + n_("Le pattern utilise pour generer le DN de l'utilisateur. le '%s' est" + + " remplace par le login (obligatoire)." + + " Par exemple: 'uid=%s,ou=People,dc=codelutin,dc=com'"), + null, + String.class, false, false), WIKITTY_SERVER_CONFIG( "wikitty.service.server.config", n_("Jetty server configuration file name"), Modified: trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurity.java =================================================================== --- trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurity.java 2012-01-09 15:15:11 UTC (rev 1310) +++ trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurity.java 2012-01-09 19:03:18 UTC (rev 1311) @@ -33,6 +33,7 @@ import java.util.Date; import java.util.List; import java.util.Set; +import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; @@ -54,8 +55,8 @@ import org.nuiton.wikitty.entities.WikittyUser; import org.nuiton.wikitty.entities.WikittyUserHelper; import org.nuiton.wikitty.WikittyUtil; -import org.nuiton.wikitty.search.Criteria; -import org.nuiton.wikitty.search.Search; +import org.nuiton.wikitty.query.WikittyQuery; +import org.nuiton.wikitty.query.WikittyQueryMaker; /** * @@ -78,14 +79,24 @@ /** cache de l'id du groupe AppAdmin */ transient protected String appAdminGroupId = null; + /** si non null alors un mecanisme d'authentification externe est utilise */ + protected WikittyServiceSecurityExternalAuthentication auth; + protected boolean externalAuthOnly = false; + /** * - * @param config not use currently but needed in futur + * @param config * @param ws + * @param auth si non null alors un mecanisme d'authentification externe est + * utilise */ - public WikittyServiceSecurity(ApplicationConfig config, WikittyService ws) { + public WikittyServiceSecurity(ApplicationConfig config, WikittyService ws, + WikittyServiceSecurityExternalAuthentication auth) { super(ws); + this.auth = auth; if (config != null) { + externalAuthOnly = config.getOptionAsBoolean(WikittyConfigOption. + WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_ONLY.getKey()); long timeToLogInfo = config.getOptionAsInt(WikittyConfigOption. WIKITTY_SECURITY_TIME_TO_LOG_INFO.getKey()); long timeToLogWarn = config.getOptionAsInt(WikittyConfigOption. @@ -109,35 +120,74 @@ public String login(String login, String password) { long start = TimeLog.getTime(); + Wikitty user = null; String tokenId; - Criteria criteria = Search.query() - .eq(WikittyUser.FQ_FIELD_WIKITTYUSER_LOGIN, login).criteria(); - String userId = getDelegate().findByCriteria(null, + WikittyQuery criteria = new WikittyQueryMaker() + .eq(WikittyUser.FQ_FIELD_WIKITTYUSER_LOGIN, login).end(); + String userId = getDelegate().findByQuery(null, Collections.singletonList(criteria)).get(0); - if (userId == null) { - throw new IllegalArgumentException(String.format( - "no such account '%s'", login)); - } else { - Wikitty user = WikittyServiceEnhanced.restore( - getDelegate(), null, userId); - // check password is valid - if (WikittyUserHelper.getPassword(user).equals(password)) { - tokenId = WikittyUtil.genSecurityTokenId(); - Wikitty wikittyToken = new WikittyImpl(tokenId); - // force add extension to wikitty - WikittyTokenHelper.addExtension(wikittyToken); - WikittyTokenHelper.setUser(wikittyToken, user.getId()); - WikittyTokenHelper.setDate(wikittyToken, new Date()); - getDelegate().store(null, Arrays.asList(wikittyToken), false); - if (log.isDebugEnabled()) { - log.debug(String.format("token '%s' is for login '%s'", - tokenId, login)); + + boolean authenticated = false; + if (auth != null) { + // on a un moyen externe d'authentification, on l'utilise en 1er + authenticated = auth.login(login, password); + if (authenticated) { + log.info(String.format( + "External authentication success for account '%s'", login)); + // authentification reussi + // si l'utilisateur n'existe pas encore on le cree + if (userId == null) { + user = new WikittyImpl(); + WikittyUserHelper.addExtension(user); + WikittyUserHelper.setLogin(user, login); + // on met un mot de passe genere car l'authentification + // est faite par un moyen externe. Si pour une raison + // celui-ci n'est pas active, il ne faut pas que l'utilisateur + // puisse se loguer avec une mot de passe reel (trouvable facilement) + String generatedPassword = "external-" + UUID.randomUUID(); + WikittyUserHelper.setPassword(user, generatedPassword); + getDelegate().store(null, Collections.singletonList(user), false); + log.info(String.format( + "Automatic user creation for account '%s'", login)); + } else { + // sinon on le charge + user = WikittyServiceEnhanced.restore( + getDelegate(), null, userId); } + } + } + + // si pas authentification externe ou l'authentification n'a pas reussi + // c'est peut-etre un utilisateur local, et qu'on authorise l'authentification + // local + if (!authenticated && !externalAuthOnly) { + if (userId == null) { + log.info(String.format("no such account '%s'", login)); } else { - throw new SecurityException("bad password"); + user = WikittyServiceEnhanced.restore( + getDelegate(), null, userId); + // check password is valid + authenticated = WikittyUserHelper.getPassword(user).equals(password); } } + + if (authenticated) { + tokenId = WikittyUtil.genSecurityTokenId(); + Wikitty wikittyToken = new WikittyImpl(tokenId); + // force add extension to wikitty + WikittyTokenHelper.addExtension(wikittyToken); + WikittyTokenHelper.setUser(wikittyToken, user.getId()); + WikittyTokenHelper.setDate(wikittyToken, new Date()); + getDelegate().store(null, Arrays.asList(wikittyToken), false); + if (log.isDebugEnabled()) { + log.debug(String.format("token '%s' is for login '%s'", + tokenId, login)); + } + } else { + throw new SecurityException("bad login or password"); + } + timeLog.log(start, "login"); return tokenId; } @@ -776,10 +826,10 @@ getDelegate(), securityToken, appAdminGroupId); if (group == null) { // 1er fois, on le recherche - Criteria criteria = Search.query() + WikittyQuery criteria = new WikittyQueryMaker() .eq(WikittyGroup.FQ_FIELD_WIKITTYGROUP_NAME, - WikittySecurityHelper.WIKITTY_APPADMIN_GROUP_NAME).criteria(); - String groupId = getDelegate().findByCriteria( + WikittySecurityHelper.WIKITTY_APPADMIN_GROUP_NAME).end(); + String groupId = getDelegate().findByQuery( securityToken, Collections.singletonList(criteria)).get(0); appAdminGroupId = groupId; group = WikittyServiceEnhanced.restore( Added: trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurityExternalAuthentication.java =================================================================== --- trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurityExternalAuthentication.java (rev 0) +++ trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurityExternalAuthentication.java 2012-01-09 19:03:18 UTC (rev 1311) @@ -0,0 +1,26 @@ +package org.nuiton.wikitty.services; + +/** + * Pour permettre de géré l'authentification par un mecanisme externe (ex:LDAP) + * a wikitty en plus du mecanisme traditionnel, il faut implanter cette + * interface et la declarer dans le fichier de configuration + * + * @author poussin + * @version $Revision$ + * + * Last update: $Date$ + * by : $Author$ + */ +public interface WikittyServiceSecurityExternalAuthentication { + + /** + * Authentifie l'utilisateur ayant pour login et password les arguments. + * La methode retourne true, si l'utilisateur a pu etre authentifier. + * + * @param login + * @param password + * @return true si l'utilisateur existe et que le mot de passe est le bon + */ + public boolean login(String login, String password); + +} Added: trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurityExternalAuthenticationLDAP.java =================================================================== --- trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurityExternalAuthenticationLDAP.java (rev 0) +++ trunk/wikitty-api/src/main/java/org/nuiton/wikitty/services/WikittyServiceSecurityExternalAuthenticationLDAP.java 2012-01-09 19:03:18 UTC (rev 1311) @@ -0,0 +1,81 @@ +package org.nuiton.wikitty.services; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.nuiton.util.ApplicationConfig; +import org.nuiton.wikitty.WikittyConfigOption; + +/** + * + * @author poussin + * @version $Revision$ + * + * Last update: $Date$ + * by : $Author$ + */ +public class WikittyServiceSecurityExternalAuthenticationLDAP + implements WikittyServiceSecurityExternalAuthentication{ + + /** to use log facility, just put in your code: log.info(\"...\"); */ + static private Log log = LogFactory.getLog(WikittyServiceSecurityExternalAuthenticationLDAP.class); + + protected ApplicationConfig config; + protected Properties jndiProp; + protected String ldapLoginPattern; + + public WikittyServiceSecurityExternalAuthenticationLDAP(ApplicationConfig config) { + this.config = config; + + // on charge toutes les options jndi + Properties jndiPropTmp = config.getOptionStartsWith(WikittyConfigOption + .WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_JNDI.getKey()); + + jndiProp = new Properties(); + for (Enumeration<String> e=(Enumeration<String>)jndiPropTmp.propertyNames(); e.hasMoreElements();) { + String key = e.nextElement(); + String value = jndiPropTmp.getProperty(key); + int debut = WikittyConfigOption.WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_JNDI.getKey().length(); + key = key.substring(debut); + jndiProp.setProperty(key, value); + } + + // on charge l'url du serveur ldap + String serverUrl = config.getOption( + WikittyConfigOption.WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_SERVER.getKey()); + jndiProp.put(Context.PROVIDER_URL, serverUrl); + + // on charge le pattern des logins (DN) + ldapLoginPattern = config.getOption( + WikittyConfigOption.WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_LOGIN_PATTERN.getKey()); + } + + public boolean login(String login, String password) { + boolean result = false; + String ldapLogin = String.format(ldapLoginPattern, login); + + // on fait une copie pour etre thread safe (si plusieurs thread appele + // cette methode en meme temps + Hashtable<String, String> env = new Hashtable(jndiProp); + env.put(Context.SECURITY_PRINCIPAL, ldapLogin); + env.put(Context.SECURITY_CREDENTIALS, password); + + try { + DirContext dirContext = new InitialDirContext(env); + dirContext.close(); + result = true; + } catch (NamingException eee) { + log.debug(String.format( + "Erreur lors de l'acces au serveur LDAP pour l'utilisateur %s -> %s", + login, ldapLogin), eee); + } + return result; + } + +} Modified: trunk/wikitty-api/src/test/java/org/nuiton/wikitty/layers/WikittyServiceSecurityTest.java =================================================================== --- trunk/wikitty-api/src/test/java/org/nuiton/wikitty/layers/WikittyServiceSecurityTest.java 2012-01-09 15:15:11 UTC (rev 1310) +++ trunk/wikitty-api/src/test/java/org/nuiton/wikitty/layers/WikittyServiceSecurityTest.java 2012-01-09 19:03:18 UTC (rev 1311) @@ -56,6 +56,7 @@ public class WikittyServiceSecurityTest extends AbstractWikittyServiceTest { // FIXME 20101112 poussin classe a revoir suite au refactoring de la secu + // et surtout la mettre dans son package private static final Log log = LogFactory.getLog(WikittyServiceSecurityTest.class); @@ -73,7 +74,7 @@ WikittyService inMemoryService = new WikittyServiceInMemory(config); WikittyServiceSecurity securityService = - new WikittyServiceSecurity(config, inMemoryService); + new WikittyServiceSecurity(config, inMemoryService, null); /** / // TODO 20101005 bleny implementation should be able to allow Added: trunk/wikitty-api/src/test/java/org/nuiton/wikitty/services/WikittyServiceSecurityTest.java =================================================================== --- trunk/wikitty-api/src/test/java/org/nuiton/wikitty/services/WikittyServiceSecurityTest.java (rev 0) +++ trunk/wikitty-api/src/test/java/org/nuiton/wikitty/services/WikittyServiceSecurityTest.java 2012-01-09 19:03:18 UTC (rev 1311) @@ -0,0 +1,51 @@ +package org.nuiton.wikitty.services; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.junit.Ignore; +import org.junit.Test; +import org.nuiton.util.ApplicationConfig; +import org.nuiton.wikitty.WikittyClient; +import org.nuiton.wikitty.WikittyConfig; +import org.nuiton.wikitty.WikittyConfigOption; + +/** + * + * @author poussin + * @version $Revision$ + * + * Last update: $Date$ + * by : $Author$ + */ +public class WikittyServiceSecurityTest { + + /** to use log facility, just put in your code: log.info(\"...\"); */ + static private Log log = LogFactory.getLog(WikittyServiceSecurityTest.class); + + @Test + @Ignore // on l'ignore car il faut avoir accept a un LDAP et mettre en clair un mot de passe :( + public void testLDAP() throws Exception { + ApplicationConfig config = WikittyConfig.getConfig(); + + String services = config.getOption(WikittyConfigOption.WIKITTY_WIKITTYSERVICE_COMPONENTS.getKey()); + services += "," + WikittyServiceSecurity.class.getName(); + config.setOption(WikittyConfigOption.WIKITTY_WIKITTYSERVICE_COMPONENTS.getKey(), + services); + config.setOption(WikittyConfigOption.WIKITTY_WIKITTYSERVICESECURITY_COMPONENTS.getKey(), + WikittyServiceSecurityExternalAuthenticationLDAP.class.getName()); + + config.setOption(WikittyConfigOption + .WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_SERVER.getKey(), + "ldap://intranet:389"); + config.setOption(WikittyConfigOption + .WIKITTY_SECURITY_EXTERNAL_AUTHENTICATION_LDAP_LOGIN_PATTERN.getKey(), + "uid=%s,ou=People,dc=codelutin,dc=home"); + WikittyClient client = new WikittyClient(config); + client.login("poussin", "xxxx"); + System.out.println("token:" + client.getSecurityToken()); + System.out.println("user: " + client.getUser()); + + System.out.println("config: " + config.getFlatOptions()); + + } +}