/*
* Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wso2.carbon.identity.thrift.authentication.internal;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.core.services.util.CarbonAuthenticationUtil;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.thrift.authentication.ThriftAuthenticatorService;
import org.wso2.carbon.identity.thrift.authentication.dao.ThriftSessionDAO;
import org.wso2.carbon.identity.thrift.authentication.internal.generatedCode.AuthenticationException;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.ServerConstants;
import org.wso2.carbon.utils.ThriftSession;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* This is a utility class that performs authentication related functionality
* by talking to back end authentication service.
*/
public class ThriftAuthenticatorServiceImpl implements ThriftAuthenticatorService {
private static final Log log = LogFactory.getLog(ThriftAuthenticatorServiceImpl.class);
//session timeout in milli seconds
private static long thriftSessionTimeOut;
private RealmService realmService;
private Map<String, ThriftSession> authenticatedSessions =
new ConcurrentHashMap<String, ThriftSession>();
private ThriftSessionDAO thriftSessionDAO;
public ThriftAuthenticatorServiceImpl(RealmService realmService, ThriftSessionDAO thriftSessionDAO, long thriftSessionTimeOut) {
this.realmService = realmService;
setThriftSessionTimeOut(thriftSessionTimeOut);
this.thriftSessionDAO = thriftSessionDAO.getInstance();
}
private void addThriftSession(ThriftSession thriftSession) throws IdentityException {
//add to cache
authenticatedSessions.put(thriftSession.getSessionId(), thriftSession);
//add to database
ThriftSessionDAO sessionDAO = this.thriftSessionDAO.getInstance();
sessionDAO.addSession(thriftSession);
}
private void removeThriftSession(String thriftSessionId) throws IdentityException {
//remove from cache
authenticatedSessions.remove(thriftSessionId);
//remove from db
ThriftSessionDAO sessionDAO = this.thriftSessionDAO.getInstance();
sessionDAO.removeSession(thriftSessionId);
}
public String authenticate(String userName, String password) throws AuthenticationException {
if (userName == null) {
logAndAuthenticationException("Authentication request was missing the user name ");
}
if (userName.indexOf("@") > 0) {
String domainName = userName.substring(userName.indexOf("@") + 1);
if (domainName == null || ("").equals(domainName.trim())) {
logAndAuthenticationException("Authentication request was missing the domain name of" +
" the user");
}
}
if (password == null) {
logAndAuthenticationException("Authentication request was missing the required password");
}
String tenantDomain = MultitenantUtils.getTenantDomain(userName);
int tenantId = 0;
try {
tenantId = realmService.getTenantManager().getTenantId(tenantDomain);
} catch (UserStoreException e) {
logAndAuthenticationException("Tenant domain tenantDomain does not exist");
}
//every thread should has its own populated CC. During the deployment time we assume super tenant
PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
carbonContext.setTenantDomain(tenantDomain);
carbonContext.setTenantId(tenantId);
boolean isSuccessful;
String tenantLessUsername = MultitenantUtils.getTenantAwareUsername(userName);
try {
UserRealm userRealm = realmService.getTenantUserRealm(tenantId);
// User not found in the UserStoreManager
if (!userRealm.getUserStoreManager().isExistingUser(tenantLessUsername)) {
throw new AuthenticationException("Invalid User : " + tenantLessUsername);
}
// Check if the user is authenticated
isSuccessful = userRealm.getUserStoreManager().authenticate(tenantLessUsername, password);
// Let the engine know if the user is authenticated or not
} catch (UserStoreException e) {
throw new AuthenticationException("User not authenticated for the given username : " + tenantLessUsername);
}
if (log.isDebugEnabled()) {
if (isSuccessful) {
log.debug("User: " + userName + " was successfully authenticated..");
} else {
log.debug("Authentication failed for user: " + userName + " Hence, returning null for session id.");
}
}
if (isSuccessful) {
//if not, create a new session
String sessionId = null;
ThriftSession session = null;
try {
sessionId = UUID.randomUUID().toString();
//populate thrift session
session = new ThriftSession();
session.setSessionId(sessionId);
session.setUserName(userName);
session.setCreatedAt(System.currentTimeMillis());
session.setLastAccess(System.currentTimeMillis());
callOnSuccessAdminLogin(session);
addThriftSession(session);
} catch (Exception e) {
String errorMsg = "Error occurred while authenticating the user: " + userName;
log.error(errorMsg, e);
throw new AuthenticationException(errorMsg);
}
return sessionId;
} else {
//TODO:call onFailedLogin: just for logging purposes
throw new AuthenticationException("User '" + userName + "' not authenticated.");
}
}
public boolean isAuthenticated(String sessionId) {
if (sessionId == null) {
return false;
}
//if cache empty, try to populate from db
if (authenticatedSessions.isEmpty()) {
try {
populateSessionsFromDB();
} catch (IdentityException e) {
String error = "Error while populating thrift sessions from cache";
log.error(error, e);
} catch (Exception e) {
String error = "Error while populating thrift sessions from cache";
log.error(error, e);
}
}
//if cache not empty, check if session id existing and valid, if so, update last access time and return it.
if (!authenticatedSessions.isEmpty()) {
ThriftSessionDAO sessionDAO = this.thriftSessionDAO.getInstance();
if (authenticatedSessions.containsKey(sessionId)) {
ThriftSession thriftSessionInCache = authenticatedSessions.get(sessionId);
if (isSessionValid(thriftSessionInCache)) {
//update the last access time in cache and d
long lastAccessTime = System.currentTimeMillis();
(authenticatedSessions.get(sessionId)).setLastAccess(lastAccessTime);
try {
//if carbon context in the thrift session is not initialized, should do that now.
onSuccessLogin(thriftSessionInCache);
//put the thrift session filled with carbon context info
authenticatedSessions.put(sessionId, thriftSessionInCache);
sessionDAO.updateLastAccessTime(sessionId, lastAccessTime);
} catch (IdentityException e) {
String error = "Error while updating last access time in DB";
log.error(error, e);
} catch (Exception e) {
String error = "Error in calling on success admin login for the thrift session.";
log.error(error, e);
}
return true;
} else {
//if not valid in cache, check if valid in db
try {
ThriftSession thriftSession = sessionDAO.getSession(sessionId);
if (isSessionValid(thriftSession)) {
//update cache and return true
thriftSession.setLastAccess(System.currentTimeMillis());
onSuccessLogin(thriftSession);
authenticatedSessions.put(thriftSession.getSessionId(), thriftSession);
sessionDAO.updateLastAccessTime(sessionId, thriftSession.getLastAccess());
return true;
} else {
//remove from cache and db and return false
removeThriftSession(sessionId);
return false;
}
} catch (IdentityException e) {
String error = "Error while obtaining thrift session from database.";
log.error(error, e);
} catch (Exception e) {
String error = "Error in calling on success admin login for the thrift session.";
log.error(error, e);
}
}
} else {
//if session id not found, check in db as well, if exist in db, populate cache
try {
if (sessionDAO.isSessionExisting(sessionId)) {
ThriftSession thriftSession = sessionDAO.getSession(sessionId);
if (isSessionValid(thriftSession)) {
thriftSession.setLastAccess(System.currentTimeMillis());
onSuccessLogin(thriftSession);
authenticatedSessions.put(thriftSession.getSessionId(), thriftSession);
sessionDAO.updateLastAccessTime(sessionId, thriftSession.getLastAccess());
return true;
} else {
sessionDAO.removeSession(sessionId);
return false;
}
}
} catch (IdentityException e) {
String error = "Error while obtaining thrift session from database.";
log.error(error, e);
} catch (Exception e) {
String error = "Error in calling on success admin login for the thrift session obtained from DB.";
log.error(error, e);
}
}
}
return false;
}
public ThriftSession getSessionInfo(String sessionId) {
return authenticatedSessions.get(sessionId);
}
private void logAndAuthenticationException(String msg) throws AuthenticationException {
log.error(msg);
throw new AuthenticationException(msg);
}
private boolean isSessionValid(ThriftSession thriftSession) {
//check whether the session is expired.
return (System.currentTimeMillis() - thriftSession.getLastAccess()) < getThriftSessionTimeOut();
}
private void populateSessionsFromDB() throws Exception {
//first clear the cache
if (!authenticatedSessions.isEmpty()) {
authenticatedSessions.clear();
}
//get all sessions from db
ThriftSessionDAO sessionDAO = this.thriftSessionDAO.getInstance();
List<ThriftSession> thriftSessions = sessionDAO.getAllSessions();
//add to cache
if (CollectionUtils.isNotEmpty(thriftSessions)) {
for (ThriftSession thriftSession : thriftSessions) {
authenticatedSessions.put(thriftSession.getSessionId(), thriftSession);
}
}
}
private void callOnSuccessAdminLogin(ThriftSession session) throws Exception {
if (realmService != null) {
String tenantDomain = MultitenantUtils.getTenantDomain(session.getUserName());
int tenantId = realmService.getTenantManager().getTenantId(tenantDomain);
CarbonAuthenticationUtil.onSuccessAdminLogin(session, session.getUserName(), tenantId,
tenantDomain, "");
}
}
private void onSuccessLogin(ThriftSession authSession) throws IdentityException {
PrivilegedCarbonContext carbonContext = PrivilegedCarbonContext.getThreadLocalCarbonContext();
try {
carbonContext.setUsername((String) (authSession.getAttribute(ServerConstants.AUTHENTICATION_SERVICE_USERNAME)));
carbonContext.setTenantDomain((String) (authSession.getAttribute(MultitenantConstants.TENANT_DOMAIN)));
carbonContext.setTenantId((Integer) (authSession.getAttribute(MultitenantConstants.TENANT_ID)));
} catch (Exception e) {
String authErrorMsg = "Error populating current carbon context from thrift auth session: " + e.getMessage();
throw IdentityException.error(authErrorMsg);
}
}
public static long getThriftSessionTimeOut() {
return thriftSessionTimeOut;
}
public static void setThriftSessionTimeOut(long thriftSessionTimeOut) {
ThriftAuthenticatorServiceImpl.thriftSessionTimeOut = thriftSessionTimeOut;
}
}