/******************************************************************************* * Author: Emerson faria Nobre - emerson-faria.nobre@serpro.gov.br - january/09 * Organization: SERPRO - Servico Federal de Processamento de Dados * Description: This source code is an extension of UserProvisioningOfficer.java * New capabilities implemented: * - Authenticate user in Ldap. * - Automatically Create/Update the MailServerAccount * (tables: fnbl_email_account, fnbl_email_enable_account, * fnbl_email_push_registry) * Changes: * Author/Date/Description: * Emerson Faria Nobre - june/2009 - Inserted parameters UserFieldName and * PwdFieldName because the LDAP Server of each Company that I * need to install Funambol use diferent names for this fields. * * Changes: * Autor/Description/Date: * Lucas da Costa Silva / Sync with notes, photo, address, and the possibility * to login with any uid from the overlay and it will use * only the uid from the ldap not to add multiple principal * to the same person that use different user in the same * device. * / Sep to Oct-2009 * ******************************************************************************* * * * * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2006 - 2007 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * 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 Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package com.funambol.server.security; import java.util.Hashtable; import java.util.List; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.DirContext; import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import sun.util.logging.resources.logging; import com.funambol.email.console.dao.ConsoleDAO; import com.funambol.email.exception.DBAccessException; import com.funambol.email.exception.InboxListenerConfigException; import com.funambol.email.model.MailServer; import com.funambol.email.model.MailServerAccount; import com.funambol.email.util.Def; import com.funambol.framework.core.Authentication; import com.funambol.framework.core.Cred; import com.funambol.framework.filter.WhereClause; import com.funambol.framework.security.Sync4jPrincipal; import com.funambol.framework.server.Sync4jUser; import com.funambol.framework.server.store.NotFoundException; import com.funambol.framework.server.store.PersistentStoreException; import com.funambol.framework.tools.Base64; import com.funambol.framework.tools.beans.LazyInitBean; import com.funambol.pushlistener.service.registry.RegistryEntryStatus; import com.funambol.server.admin.AdminException; import com.funambol.server.admin.UserManager; import com.funambol.server.config.Configuration; /** * This is an implementation of the Officier interface. It provides * the user provisioning so if an user is not in the database he will be added. * It requires basic authentication * * @version $Id: UserProvisioningOfficer.java,v 1.4 2008-06-24 12:50:06 piter_may Exp $ */ public class LdapUserProvisioningOfficer extends DBOfficer implements LazyInitBean { private static final long serialVersionUID = 1978810349147209602L; Ldap objLdap; MailServerAccount msa; Boolean InsertMSA; String ldapIP; String ldapPort; String ldapStartSearchPath; boolean MsaEnablePush; boolean MsaEnablePolling; int MsaRefreshTime; int MsaMaxEmailNumber; int MsaMaxImapEmails; // ------------------------------------------------------------ Constructors public LdapUserProvisioningOfficer() { super(); msa = new MailServerAccount(); } // ---------------------------------------------------------- Public methods public void init() { super.init(); } /** * Authenticates a credential. * * @param credential the credential to be authenticated * * @return the Sync4jUser if the credential is autenticated, null otherwise */ public Sync4jUser authenticateUser(Cred credential) { if (log.isTraceEnabled()) { StringBuffer sb = new StringBuffer("##########"); sb.append(credential.getAuthentication().getPassword()); sb.append("::"); sb.append(credential.getAuthentication().getData()); sb.append("::"); sb.append(credential.getAuthentication().getUsername()); sb.append("::"); sb.append(credential.getUsername()); sb.append("::"); sb.append(credential.getData()); sb.append("::"); sb.append(credential.getFormat()); sb.append("::"); sb.append(credential.getType()); sb.append("##############"); log.trace(sb.toString()); } Configuration config = Configuration.getConfiguration(); ps = config.getStore(); userManager = (UserManager) config.getUserManager(); String type = credential.getType(); if ((Cred.AUTH_TYPE_BASIC).equals(type)) { return authenticateBasicCredential(credential,"simple"); } else if ((Cred.AUTH_TYPE_MD5).equals(type)) { return authenticateMD5Credential(credential); } return null; } protected Sync4jUser getUser(String userName, String password) { try { objLdap = new Ldap(this.getLdapIP(), this.getLdapPort(), userName, "empty", this.getLdapStartSearchPath(), "nothing is so simple"); return super.getUser(objLdap.getUid(),password); } catch (Exception e) { return null; } } /** * Gets the supported authentication type * * @return the basic authentication type */ public String getClientAuth() { return Cred.AUTH_TYPE_BASIC; } // ------------------------------------------------------- Protected Methods // Insert/Update MailServerAccount (MSA) protected void InsertUpdateMSA(String user, String pwd) { if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - I will create cdao object"); } try { ConsoleDAO cdao = new ConsoleDAO(); if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - I will verify if it is insert or update"); } msa = null; if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - before command cdao.getUser(user)"); } List accounts = cdao.getUserAccounts(user); if(accounts!=null && accounts.size()>0) { for (MailServerAccount mailServerAccount : accounts) { if(mailServerAccount.getMailServer().getDescription().equals("expresso")) { msa = accounts.get(0); } } } if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - after command cdao.getUser(user)"); } if (msa == null) { msa = new MailServerAccount(); this.InsertMSA = true; } else { this.InsertMSA = false; } if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - after if (msa == null)"); log.trace("LdapUserProvisioningOfficer - msa.getUsername() = " + msa.getUsername()); log.trace("LdapUserProvisioningOfficer - It is insert: " + this.InsertMSA + "- username = " + msa.getUsername()); } if (!this.InsertMSA) { if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - The key Id = " + msa.getId()); log.trace("LdapUserProvisioningOfficer - The key UserName = " + msa.getUsername()); } } if (this.InsertMSA) { if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - I will set setUserName" + user); } msa.setUsername(user); } msa.setMsLogin(user); msa.setMsPassword(pwd); msa.setMsAddress(objLdap.getmail()); msa.setPush(this.getMsaEnablePush()); msa.setMaxEmailNumber(this.getMsaMaxEmailNumber()); msa.setMaxImapEmail(this.getMsaMaxImapEmails()); msa.setPeriod(this.getMsaRefreshTime()); msa.setActive(this.getMsaEnablePolling()); msa.setTaskBeanFile(Def.DEFAULT_INBOX_LISTENER_BEAN_FILE); msa.setLastUpdate(System.currentTimeMillis()); if (this.InsertMSA) { msa.setStatus(RegistryEntryStatus.NEW); } else { msa.setStatus(RegistryEntryStatus.UPDATED); } if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - I already set setMsLogin" + user); log.trace("LdapUserProvisioningOfficer - I will set setMsPassword"); log.trace("LdapUserProvisioningOfficer - I will set setMsAddress = " + objLdap.getmail()); log.trace("LdapUserProvisioningOfficer - I will set setPush = " + this.getMsaEnablePush()); log.trace("LdapUserProvisioningOfficer - I will set setMaxEmailNumber = " + this.getMsaMaxEmailNumber()); log.trace("LdapUserProvisioningOfficer - I will set setMaxImapEmail = " + this.getMsaMaxImapEmails()); log.trace("LdapUserProvisioningOfficer - I will set setPeriod = " + this.getMsaRefreshTime()); log.trace("LdapUserProvisioningOfficer - I will set setActive = " + this.getMsaEnablePolling()); log.trace("LdapUserProvisioningOfficer - I will set fnbl_email_push_registry"); } String[] param = {"description"}; String[] value = {"expresso"}; String[] operator = {WhereClause.OPT_EQ}; MailServer[] ms = cdao.getPubMailServers(new WhereClause(param[0], new String[]{value[0]}, operator[0], false)); if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - I found the MailServer expresso - ID = " + ms[0].getMailServerId()); } msa.setMailServer(cdao.getPubMailServer(ms[0].getMailServerId())); if (this.InsertMSA) { int ret = cdao.insertUserAccount(msa); if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - return of insertUser(msa): " + ret); } } else { int ret = cdao.updateUserAccount(msa); if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - return of updateUser(msa): " + ret); } } } catch (InboxListenerConfigException e) { log.error("LdapUserProvisioningOfficer - Error creating DAO layer to Insert/Update Mail User: ", e); } catch (DBAccessException e) { log.error("LdapUserProvisioningOfficer - Error accessing Database to Insert/Update Mail User: ", e); } } /** * Checks the given credential. If the user or the principal isn't found, * they are created. * * @param credential the credential to check * * @return the Sync4jUser if the credential is autenticated, null otherwise */ protected Sync4jUser authenticateBasicCredential(Cred credential, String authType) { String username = null, password = null; Authentication auth = credential.getAuthentication(); String deviceId = auth.getDeviceId(); String userpwd = new String(Base64.decode(auth.getData())); int p = userpwd.indexOf(':'); if (p == -1) { username = userpwd; password = ""; } else { username = (p > 0) ? userpwd.substring(0, p) : ""; password = (p == (userpwd.length() - 1)) ? "" : userpwd.substring(p + 1); } if (log.isTraceEnabled()) { log.trace("User to check: " + username); } // LDAP Checkpoint // if (log.isTraceEnabled()) { log.trace("LdapUserProvisioningOfficer - CheckPoint LDAP - getLdapIP: " + this.getLdapIP() + " getLdapPort: " + this.getLdapPort()); } objLdap = new Ldap(this.getLdapIP(), this.getLdapPort(), username, password, this.getLdapStartSearchPath(), authType); // Try to authenticate in LDAP if (objLdap.getreturnStatus() == false) { if (log.isTraceEnabled()) { log.trace("LDAP Authentication Failure: " + objLdap.geterrorMsg() + " - " + objLdap.geterrorStatus()); } return null; } this.InsertUpdateMSA(objLdap.getUid(), password); // Gets the user Sync4jUser user = getUser(objLdap.getUid(), null); if (user == null) { try { user = insertUser(objLdap.getUid(), password); if (log.isTraceEnabled()) { log.trace(username+" to User '" + objLdap.getUid() + "' created"); } } catch (Exception e) { log.error("Error inserting a new user", e); return null; } } else { if (log.isTraceEnabled()) { log.trace(username+" to User '" + objLdap.getUid() + "' found"); } // Check the roles // if (isASyncUser(user)) { // // User authenticated if (log.isTraceEnabled()) { log.trace("User is a SyncUser"); } } else { // User not authenticated // if (log.isTraceEnabled()) { log.trace("The user is not a '" + ROLE_USER + "'"); } return null; } } // // Verify that the principal for the specify deviceId and username exists // Otherwise a new principal will be created // try { handlePrincipal(objLdap.getUid(), deviceId); } catch (PersistentStoreException e) { log.error("Error handling the principal", e); return null; } return user; } /** * Insert a new user with the given username and password * * @param userName the username * @param password the password * * @return the new user * * @throws AdminException in case of admin errors * @throws PersistentStoreException if an error occurs */ protected Sync4jUser insertUser( String userName, String password) throws AdminException, PersistentStoreException { Sync4jUser user = new Sync4jUser(); user.setUsername(userName); user.setPassword(password); user.setFirstname(objLdap.getcn()); user.setRoles(new String[]{ROLE_USER}); user.setEmail(objLdap.getmail()); userManager.insertUser(user); return user; } /** * Returns the principal with the given username and deviceId. * null if not found * @param userName the username * @param deviceId the device id * @return the principal found or null. * @throws PersistentStoreException if an error occurs */ protected Sync4jPrincipal getPrincipal(String userName, String deviceId) throws PersistentStoreException { Sync4jPrincipal principal = null; // // Verify that exist the principal for the specify deviceId and username // principal = Sync4jPrincipal.createPrincipal(userName, deviceId); try { ps.read(principal); } catch (NotFoundException ex) { return null; } return principal; } /** * Inserts a new principal with the given userName and deviceId * @param userName the username * @param deviceId the device id * @return the principal created * @throws PersistentStoreException if an error occurs creating the principal */ protected Sync4jPrincipal insertPrincipal(String userName, String deviceId) throws PersistentStoreException { // // We must create a new principal // Sync4jPrincipal principal = Sync4jPrincipal.createPrincipal(userName, deviceId); ps.store(principal); return principal; } /** * Searchs if there is a principal with the given username and device id. * if no principal is found, a new one is created. * @param userName the user name * @param deviceId the device id * @return the found principal or the new one */ protected Sync4jPrincipal handlePrincipal(String username, String deviceId) throws PersistentStoreException { Sync4jPrincipal principal = null; // // Verify if the principal for the specify deviceId and username exists // principal = getPrincipal(username, deviceId); if (log.isTraceEnabled()) { log.trace("Principal '" + username + "/" + deviceId + "' " + ((principal != null) ? "found" : "not found. A new principal will be created")); } if (principal == null) { principal = insertPrincipal(username, deviceId); if (log.isTraceEnabled()) { log.trace("Principal '" + username + "/" + deviceId + "' created"); } } return principal; } public void setLdapIP(String pldapIP) { this.ldapIP = pldapIP; } public String getLdapIP() { return this.ldapIP; } public void setLdapPort(String pLdapPort) { this.ldapPort = pLdapPort; } public String getLdapPort() { return this.ldapPort; } public void setLdapStartSearchPath(String pLdapStartSearchPath) { this.ldapStartSearchPath = pLdapStartSearchPath; } public String getLdapStartSearchPath() { return this.ldapStartSearchPath; } public void setMsaEnablePush(boolean pMsaEnablePush) { this.MsaEnablePush = pMsaEnablePush; } public boolean getMsaEnablePush() { return this.MsaEnablePush; } public void setMsaEnablePolling(boolean pMsaEnablePolling) { this.MsaEnablePolling = pMsaEnablePolling; } public boolean getMsaEnablePolling() { return this.MsaEnablePolling; } public void setMsaRefreshTime(int pMsaRefreshTime) { this.MsaRefreshTime = pMsaRefreshTime; } public int getMsaRefreshTime() { return this.MsaRefreshTime; } public void setMsaMaxEmailNumber(int pMsaMaxEmailNumber) { this.MsaMaxEmailNumber = pMsaMaxEmailNumber; } public int getMsaMaxEmailNumber() { return this.MsaMaxEmailNumber; } public void setMsaMaxImapEmails(int pMsaMaxImapEmails) { this.MsaMaxImapEmails = pMsaMaxImapEmails; } public int getMsaMaxImapEmails() { return this.MsaMaxImapEmails; } } // Class to manage LDAP class Ldap { private String ldapServer; private String ldapPort; private String ldapSearchPath; private String UserID; private String userDN; private String pwd; private String cn; private String mail; private String errorMsg; private String errorStatus; private boolean returnStatus; private String uid; private String authtype; public Ldap(String ldapServer, String ldapPort, String UserID, String pwd, String ldapSearchPath, String authType) { this.ldapServer = ldapServer; this.ldapPort = ldapPort; this.UserID = UserID; this.pwd = pwd; this.ldapSearchPath = ldapSearchPath; this.authtype = authType; this.returnStatus = processLDAP(); } private boolean processLDAP() { // Password cannot be null if ((pwd.trim().length()) == 0) { this.errorMsg = "Password Cannot be null"; this.errorStatus = "nullPwd"; return false; } // Connecting as anonymous to get information about the user Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://" + this.ldapServer + ":" + this.ldapPort); try { // Connecting DirContext ctx = new InitialDirContext(env); // Searching User SearchControls ctls = new SearchControls(); ctls.setSearchScope(SearchControls.SUBTREE_SCOPE); ctls.setCountLimit(1); ctls.setTimeLimit(10000); // max 10 seconds String filter = "(uid=" + this.UserID + ")"; NamingEnumeration answer = ctx.search(this.getLdapSearchPath(), filter, ctls); if (answer.hasMore()) { // just the first one SearchResult sr = (SearchResult) answer.next(); this.userDN = sr.getName() + "," + this.getLdapSearchPath(); try { // Getting User attributes this.cn = sr.getAttributes().get("cn").get(0).toString(); this.mail = sr.getAttributes().get("mail").get(0).toString(); this.uid = sr.getAttributes().get("uid").get(0).toString(); } catch (Exception e) { e.printStackTrace(); this.errorMsg = e.toString(); this.errorStatus = "notGetAttributes"; return false; } } else { this.errorMsg = "User/Password not Found in LDAP"; this.errorStatus = "notFound"; return false; } } catch (NamingException e) { e.printStackTrace(); this.errorMsg = e.toString(); this.errorStatus = "notConnect"; return false; } // only simple auth is suported in ldap if("simple".equals(authtype)) { // Binding (Verifing Credentials) Hashtable env2 = new Hashtable(); env2.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env2.put(Context.PROVIDER_URL, "ldap://" + this.ldapServer + ":" + this.ldapPort); env2.put(Context.SECURITY_AUTHENTICATION, authtype); env2.put(Context.SECURITY_PRINCIPAL, this.userDN); // specify the user dn env2.put(Context.SECURITY_CREDENTIALS, this.pwd); // specify the password try { DirContext ctx = new InitialDirContext(env2); ctx.close(); } catch (NamingException e) { e.printStackTrace(); this.errorMsg = e.toString(); this.errorStatus = "notBind"; return false; } } return true; } // Getters methods public String getldapServer() { return this.ldapServer; } public String getldapPort() { return this.ldapPort; } public String getUserID() { return this.UserID; } public String getuserDN() { return this.userDN; } public String getcn() { return this.cn; } public String getmail() { return this.mail; } public String geterrorMsg() { return this.errorMsg; } public String geterrorStatus() { return this.errorStatus; } public boolean getreturnStatus() { return this.returnStatus; } public String getLdapSearchPath() { return this.ldapSearchPath; } public String getUid() { return this.uid; } }