/*******************************************************************************
* 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;
}
}