/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.services;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import org.teiid.adminapi.VDB;
import org.teiid.adminapi.VDB.ConnectionType;
import org.teiid.adminapi.impl.SessionMetadata;
import org.teiid.adminapi.impl.VDBMetaData;
import org.teiid.client.security.InvalidSessionException;
import org.teiid.client.security.LogonException;
import org.teiid.client.security.SessionToken;
import org.teiid.core.CoreConstants;
import org.teiid.core.util.ArgCheck;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.deployers.VDBRepository;
import org.teiid.dqp.internal.process.DQPCore;
import org.teiid.dqp.internal.process.DQPWorkContext;
import org.teiid.dqp.service.SessionService;
import org.teiid.dqp.service.SessionServiceException;
import org.teiid.logging.AuditMessage;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.net.ServerConnection;
import org.teiid.net.TeiidURL;
import org.teiid.net.socket.AuthenticationType;
import org.teiid.runtime.RuntimePlugin;
import org.teiid.security.Credentials;
import org.teiid.security.GSSResult;
import org.teiid.security.SecurityHelper;
import org.teiid.vdb.runtime.VDBKey;
/**
* This class serves as the primary implementation of the Session Service.
*/
public class SessionServiceImpl implements SessionService {
public static final String GSS_PATTERN_PROPERTY = "gss-pattern"; //$NON-NLS-1$
public static final String PASSWORD_PATTERN_PROPERTY = "password-pattern"; //$NON-NLS-1$
public static final String SECURITY_DOMAIN_PROPERTY = "security-domain"; //$NON-NLS-1$
public static final String AUTHENTICATION_TYPE_PROPERTY = "authentication-type"; //$NON-NLS-1$
public static final String AT = "@"; //$NON-NLS-1$
/*
* Configuration state
*/
private long sessionMaxLimit = DEFAULT_MAX_SESSIONS;
private long sessionExpirationTimeLimit = DEFAULT_SESSION_EXPIRATION;
private AuthenticationType defaultAuthenticationType = AuthenticationType.USERPASSWORD;
private static boolean CHECK_PING = PropertiesUtils.getBooleanProperty(System.getProperties(), "org.teiid.checkPing", true); //$NON-NLS-1$
/*
* Injected state
*/
private VDBRepository vdbRepository;
protected SecurityHelper securityHelper;
private DQPCore dqp;
private Map<String, SessionMetadata> sessionCache = new ConcurrentHashMap<String, SessionMetadata>();
private Timer sessionMonitor = null;
private String securityDomainName;
private boolean trustAllLocal = true;
private boolean allowSecurityDomainQualifier;
public void setSecurityDomain(String domainName) {
this.securityDomainName = domainName;
}
// -----------------------------------------------------------------------------------
// S E R V I C E - R E L A T E D M E T H O D S
// -----------------------------------------------------------------------------------
private void monitorSessions() {
long currentTime = System.currentTimeMillis();
for (SessionMetadata info : sessionCache.values()) {
try {
if (CHECK_PING && !info.isEmbedded() && currentTime - info.getLastPingTime() > ServerConnection.PING_INTERVAL * 5) {
LogManager.logInfo(LogConstants.CTX_SECURITY, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40007, info.getSessionId()));
closeSession(info.getSessionId());
} else if (sessionExpirationTimeLimit > 0 && currentTime - info.getCreatedTime() > sessionExpirationTimeLimit) {
LogManager.logInfo(LogConstants.CTX_SECURITY,RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40008, info.getSessionId()));
closeSession(info.getSessionId());
}
} catch (Exception e) {
LogManager.logDetail(LogConstants.CTX_SECURITY, e, "error running session monitor, unable to monitor:", info.getSessionId()); //$NON-NLS-1$
}
}
}
@Override
public void closeSession(String sessionID) throws InvalidSessionException {
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_SECURITY, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_SECURITY, new Object[] {"closeSession", sessionID}); //$NON-NLS-1$
}
SessionMetadata info = getSessionInfo(sessionID, true);
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_AUDITLOGGING, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_AUDITLOGGING, new AuditMessage("session", "logoff", info)); //$NON-NLS-1$ //$NON-NLS-2$
}
if (info.getVDBName() != null) {
try {
dqp.terminateSession(info.getSessionId());
} catch (Exception e) {
LogManager.logWarning(LogConstants.CTX_SECURITY,e, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40018));
}
}
info.setSecurityContext(null);
info.setClosed();
info.getSessionVariables().clear();
}
@Override
public SessionMetadata createSession(String vdbName,
String vdbVersion, AuthenticationType authType, String userName,
Credentials credentials, String applicationName, Properties properties)
throws LoginException, SessionServiceException {
ArgCheck.isNotNull(applicationName);
ArgCheck.isNotNull(properties);
Object securityContext = null;
Subject subject = null;
String hostName = properties.getProperty(TeiidURL.CONNECTION.CLIENT_HOSTNAME);
String ipAddress = properties.getProperty(TeiidURL.CONNECTION.CLIENT_IP_ADDRESS);
String clientMac = properties.getProperty(TeiidURL.CONNECTION.CLIENT_MAC);
boolean onlyAllowPassthrough = Boolean.valueOf(properties.getProperty(TeiidURL.CONNECTION.PASSTHROUGH_AUTHENTICATION,
"false")); //$NON-NLS-1$
AuditMessage.LogonInfo info = new AuditMessage.LogonInfo(vdbName, vdbVersion, authType.toString(), userName, applicationName, hostName, ipAddress, clientMac, onlyAllowPassthrough);
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_AUDITLOGGING, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_AUDITLOGGING, new AuditMessage("session", "logon-request", info, null)); //$NON-NLS-1$ //$NON-NLS-2$
}
try {
// Validate VDB and version if logging on to server product...
VDBMetaData vdb = null;
if (vdbName != null) {
vdb = getActiveVDB(vdbName, vdbVersion);
if (vdb == null) {
throw new SessionServiceException(RuntimePlugin.Event.TEIID40046, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40046, vdbName, vdbVersion));
}
}
if (sessionMaxLimit > 0 && getActiveSessionsCount() >= sessionMaxLimit) {
throw new SessionServiceException(RuntimePlugin.Event.TEIID40043, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40043, new Long(sessionMaxLimit)));
}
String securityDomain = getSecurityDomain(userName, vdbName, vdbVersion, vdb);
if (securityDomain != null) {
// Authenticate user...
// if not authenticated, this method throws exception
LogManager.logDetail(LogConstants.CTX_SECURITY, new Object[] {"authenticateUser", userName, applicationName}); //$NON-NLS-1$
String baseUserName = userName;
if (allowSecurityDomainQualifier) {
baseUserName = getBaseUsername(userName);
}
if (onlyAllowPassthrough || authType.equals(AuthenticationType.GSS)) {
subject = this.securityHelper.getSubjectInContext(securityDomain);
if (subject == null) {
if ((!onlyAllowPassthrough || !(trustAllLocal && DQPWorkContext.getWorkContext().isLocal()))) {
throw new LoginException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40087));
}
} else {
userName = getUserName(subject, baseUserName);
}
securityContext = this.securityHelper.getSecurityContext();
} else {
userName = baseUserName;
securityContext = this.securityHelper.authenticate(securityDomain, baseUserName, credentials, applicationName);
subject = this.securityHelper.getSubjectInContext(securityContext);
}
}
else {
LogManager.logDetail(LogConstants.CTX_SECURITY, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40117));
}
long creationTime = System.currentTimeMillis();
// Return a new session info object
SessionMetadata newSession = new SessionMetadata();
newSession.setSessionToken(new SessionToken(userName));
newSession.setSessionId(newSession.getSessionToken().getSessionID());
newSession.setUserName(userName);
newSession.setCreatedTime(creationTime);
newSession.setApplicationName(applicationName);
newSession.setClientHostName(hostName);
newSession.setIPAddress(ipAddress);
newSession.setClientHardwareAddress(clientMac);
newSession.setSecurityDomain(securityDomain);
if (vdb != null) {
newSession.setVDBName(vdb.getName());
newSession.setVDBVersion(vdb.getVersion());
}
// these are local no need for monitoring.
newSession.setSubject(subject);
newSession.setSecurityContext(securityContext);
newSession.setVdb(vdb);
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_SECURITY, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_SECURITY, new Object[] {"Logon successful, created", newSession }); //$NON-NLS-1$
}
this.sessionCache.put(newSession.getSessionId(), newSession);
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_AUDITLOGGING, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_AUDITLOGGING, new AuditMessage("session", "logon-success", newSession)); //$NON-NLS-1$ //$NON-NLS-2$
}
return newSession;
} catch (LoginException e) {
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_AUDITLOGGING, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_AUDITLOGGING, new AuditMessage("session", "logon-fail", info, e)); //$NON-NLS-1$ //$NON-NLS-2$
}
throw e;
} catch (SessionServiceException e) {
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_AUDITLOGGING, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_AUDITLOGGING, new AuditMessage("session", "logon-fail", info, e)); //$NON-NLS-1$ //$NON-NLS-2$
}
throw e;
}
}
/**
*
* @param vdbName
* @param vdbVersion
* @return the vdb or null if it doesn't exist
* @throws SessionServiceException if the version is not valid or the vdb doesn't accept connections
*/
protected VDBMetaData getActiveVDB(String vdbName, String vdbVersion) throws SessionServiceException {
VDBMetaData vdb = null;
try {
if (vdbVersion == null) {
vdb = this.vdbRepository.getLiveVDB(vdbName);
}
else {
vdb = this.vdbRepository.getLiveVDB(vdbName, vdbVersion);
}
} catch (NumberFormatException e) {
throw new SessionServiceException(RuntimePlugin.Event.TEIID40045, e, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40045, vdbVersion));
}
if (vdb == null) {
return null;
}
if (vdb.getConnectionType() == ConnectionType.NONE) {
throw new SessionServiceException(RuntimePlugin.Event.TEIID40048, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40048, vdbName, vdbVersion));
}
return vdb;
}
@Override
public Collection<SessionMetadata> getActiveSessions() {
return new ArrayList<SessionMetadata>(this.sessionCache.values());
}
@Override
public SessionMetadata getActiveSession(String sessionID) {
return this.sessionCache.get(sessionID);
}
@Override
public int getActiveSessionsCount() throws SessionServiceException{
return this.sessionCache.size();
}
@Override
public Collection<SessionMetadata> getSessionsLoggedInToVDB(VDBKey key) {
ArrayList<SessionMetadata> results = new ArrayList<SessionMetadata>();
for (SessionMetadata info : this.sessionCache.values()) {
if (info.getVdb() != null && key.equals(info.getVdb().getAttachment(VDBKey.class))) {
results.add(info);
}
}
return results;
}
@Override
public void pingServer(String sessionID) throws InvalidSessionException {
SessionMetadata info = getSessionInfo(sessionID, false);
info.setLastPingTime(System.currentTimeMillis());
this.sessionCache.put(sessionID, info);
LogManager.logDetail(LogConstants.CTX_SECURITY, "Keep-alive ping received for session:", sessionID); //$NON-NLS-1$
}
@Override
public boolean terminateSession(String terminatedSessionID, String adminSessionID) {
Object[] params = {adminSessionID, terminatedSessionID};
LogManager.logInfo(LogConstants.CTX_SECURITY, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40009, params));
try {
closeSession(terminatedSessionID);
return true;
} catch (InvalidSessionException e) {
LogManager.logDetail(LogConstants.CTX_SECURITY,e,e.getMessage());
return false;
}
}
@Override
public SessionMetadata validateSession(String sessionID) throws InvalidSessionException, SessionServiceException {
SessionMetadata info = getSessionInfo(sessionID, false);
return info;
}
private SessionMetadata getSessionInfo(String sessionID, boolean remove)
throws InvalidSessionException {
if (sessionID == null) {
throw new InvalidSessionException(RuntimePlugin.Event.TEIID40041, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40041));
}
SessionMetadata info = remove?this.sessionCache.remove(sessionID):this.sessionCache.get(sessionID);
if (info == null) {
throw new InvalidSessionException(RuntimePlugin.Event.TEIID40042, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40042, sessionID));
}
return info;
}
public long getSessionMaxLimit() {
return this.sessionMaxLimit;
}
public void setSessionMaxLimit(long limit) {
this.sessionMaxLimit = limit;
}
public long getSessionExpirationTimeLimit() {
return this.sessionExpirationTimeLimit;
}
public void setSessionExpirationTimeLimit(long limit) {
this.sessionExpirationTimeLimit = limit;
}
public void setAuthenticationType(AuthenticationType flag) {
this.defaultAuthenticationType = flag;
}
public void start() {
LogManager.logDetail(LogConstants.CTX_SECURITY, new Object[] {"Default security domain configured=", this.securityDomainName}); //$NON-NLS-1$
this.sessionMonitor = new Timer("SessionMonitor", true); //$NON-NLS-1$
this.sessionMonitor.schedule(new TimerTask() {
@Override
public void run() {
monitorSessions();
}
}, ServerConnection.PING_INTERVAL * 3, ServerConnection.PING_INTERVAL * 2);
}
public void stop(){
if (sessionMonitor != null) {
this.sessionMonitor.cancel();
}
for (SessionMetadata session : sessionCache.values()) {
try {
closeSession(session.getSessionId());
} catch (InvalidSessionException e) {
}
}
}
public void setVDBRepository(VDBRepository repo) {
this.vdbRepository = repo;
}
public void setSecurityHelper(SecurityHelper securityHelper) {
this.securityHelper = securityHelper;
}
public void setDqp(DQPCore dqp) {
this.dqp = dqp;
}
@Override
public SecurityHelper getSecurityHelper() {
return securityHelper;
}
static String getBaseUsername(String username) {
if (username == null) {
return username;
}
int index = getQualifierIndex(username);
String result = username;
if (index != -1) {
result = username.substring(0, index);
}
//strip the escape character from the remaining ats
return result.replaceAll("\\\\"+AT, AT); //$NON-NLS-1$
}
static String getDomainName(String username) {
if (username == null) {
return username;
}
int index = getQualifierIndex(username);
if (index != -1) {
return username.substring(index + 1);
}
return null;
}
static int getQualifierIndex(String username) {
int index = username.length();
while ((index = username.lastIndexOf(AT, --index)) != -1) {
if (index > 0 && username.charAt(index - 1) != '\\') {
return index;
}
}
return -1;
}
@Override
public AuthenticationType getAuthenticationType(String vdbName, String version, String userName) throws LogonException {
if (userName == null) {
userName = CoreConstants.DEFAULT_ANON_USERNAME;
}
if (vdbName != null) {
VDB vdb = null;
try {
vdb = getActiveVDB(vdbName, version);
} catch (SessionServiceException e) {
throw new LogonException(e);
}
if (vdb != null) {
String gssPattern = vdb.getPropertyValue(GSS_PATTERN_PROPERTY);
//TODO: cache the patterns
if (gssPattern != null && Pattern.matches(gssPattern, userName)) {
return AuthenticationType.GSS;
}
String passwordPattern = vdb.getPropertyValue(PASSWORD_PATTERN_PROPERTY);
if (passwordPattern != null && Pattern.matches(passwordPattern, userName)) {
return AuthenticationType.USERPASSWORD;
}
String typeProperty = vdb.getPropertyValue(AUTHENTICATION_TYPE_PROPERTY);
if (typeProperty != null) {
return AuthenticationType.valueOf(typeProperty);
}
}
}
return this.defaultAuthenticationType;
}
public String getSecurityDomain(String userName, String vdbName, String version, VDB vdb) throws LoginException {
String securityDomain = null;
if (allowSecurityDomainQualifier) {
securityDomain = getDomainName(userName);
}
if (vdbName != null) {
try {
if (vdb == null) {
vdb = getActiveVDB(vdbName, version);
}
if (vdb != null) {
String typeProperty = vdb.getPropertyValue(SECURITY_DOMAIN_PROPERTY);
if (typeProperty != null) {
if (securityDomain != null && !typeProperty.equals(securityDomain)) {
//conflicting
throw new LoginException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40116));
}
return typeProperty;
}
}
} catch (SessionServiceException e) {
// ignore and return default, this only occur if the name and version are wrong
}
}
if (securityDomain != null) {
if (this.securityDomainName != null && this.securityDomainName.equals(securityDomain)) {
return securityDomain;
}
throw new LoginException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40116));
}
return this.securityDomainName;
}
@Override
public GSSResult neogitiateGssLogin(String user, String vdbName,
String vdbVersion, byte[] serviceTicket) throws LoginException, LogonException {
String securityDomain = getSecurityDomain(user, vdbName, vdbVersion, null);
if (securityDomain == null ) {
throw new LogonException(RuntimePlugin.Event.TEIID40059, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40059));
}
return this.securityHelper.negotiateGssLogin(securityDomain, serviceTicket);
}
public AuthenticationType getDefaultAuthenticationType() {
return defaultAuthenticationType;
}
private String getUserName(Subject subject, String userName) {
Set<Principal> principals = subject.getPrincipals();
for (Principal p:principals) {
if (p instanceof Group) {
continue;
}
return p.getName();
}
return userName;
}
public boolean isTrustAllLocal() {
return trustAllLocal;
}
public void setTrustAllLocal(boolean trustAllLocal) {
this.trustAllLocal = trustAllLocal;
}
public void setAllowSecurityDomainQualifier(boolean useSecurityDomainQualifier) {
this.allowSecurityDomainQualifier = useSecurityDomainQualifier;
}
public boolean isAllowSecurityDomainQualifier() {
return allowSecurityDomainQualifier;
}
}