[New] Implemented keepAlive of strolch sessions

This commit is contained in:
Robert von Burg 2020-05-11 17:48:38 +02:00
parent 8f645d1af7
commit 5e5289cbc8
19 changed files with 489 additions and 175 deletions

View File

@ -141,15 +141,15 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
@Override
public Certificate authenticate(String username, char[] password) {
assertContainerStarted();
Certificate certificate = this.privilegeHandler.authenticate(username, password);
Certificate certificate = this.privilegeHandler.authenticate(username, password, false);
writeAudit(certificate, LOGIN, AccessType.CREATE, username);
return certificate;
}
@Override
public Certificate authenticate(String username, char[] password, String source, Usage usage) {
public Certificate authenticate(String username, char[] password, String source, Usage usage, boolean keepAlive) {
assertContainerStarted();
Certificate certificate = this.privilegeHandler.authenticate(username, password, source, usage);
Certificate certificate = this.privilegeHandler.authenticate(username, password, source, usage, keepAlive);
writeAudit(certificate, LOGIN, AccessType.CREATE, username);
return certificate;
}
@ -157,7 +157,7 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
@Override
public Certificate authenticateSingleSignOn(Object data) {
assertContainerStarted();
Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data);
Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data, false);
writeAudit(certificate, LOGIN, AccessType.CREATE, certificate.getUsername());
return certificate;
}
@ -165,11 +165,24 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
@Override
public Certificate authenticateSingleSignOn(Object data, String source) {
assertContainerStarted();
Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data, source);
Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data, source, false);
writeAudit(certificate, LOGIN, AccessType.CREATE, certificate.getUsername());
return certificate;
}
@Override
public Certificate refreshSession(Certificate certificate, String source) {
assertContainerStarted();
Certificate refreshedCert = this.privilegeHandler.refresh(certificate, source);
writeAudit(refreshedCert, LOGIN, AccessType.CREATE, refreshedCert.getUsername());
return refreshedCert;
}
@Override
public boolean isRefreshAllowed() {
return this.privilegeHandler.isRefreshAllowed();
}
private void writeAudit(Certificate certificate, String login, AccessType accessType, String username) {
StrolchRealm realm = getContainer().getRealm(certificate);
try (StrolchTransaction tx = realm.openTx(certificate, login, false).silentThreshold(1, NANOSECONDS)) {

View File

@ -45,7 +45,7 @@ public interface PrivilegeHandler {
*
* @return the certificate
*
* @see li.strolch.privilege.handler.PrivilegeHandler#authenticate(String, char[])
* @see li.strolch.privilege.handler.PrivilegeHandler#authenticate(String, char[], boolean)
*/
Certificate authenticate(String username, char[] password);
@ -60,12 +60,14 @@ public interface PrivilegeHandler {
* the source of the request
* @param usage
* the usage for this authentication
* @param keepAlive
* should this session be kept alive
*
* @return the certificate
*
* @see li.strolch.privilege.handler.PrivilegeHandler#authenticate(String, char[])
* @see li.strolch.privilege.handler.PrivilegeHandler#authenticate(String, char[], boolean)
*/
Certificate authenticate(String username, char[] password, String source, Usage usage);
Certificate authenticate(String username, char[] password, String source, Usage usage, boolean keepAlive);
/**
* Authenticates a user on a remote Single Sign On service. This is implemented by the
@ -95,6 +97,25 @@ public interface PrivilegeHandler {
*/
Certificate authenticateSingleSignOn(Object data, String source) throws PrivilegeException;
/**
* Performs a refresh of the given certificate's session by returning a new certificate
*
* @param certificate
* the certificate to refresh
* @param source
* the source of the request
*
* @return certificate a new certificate
*/
Certificate refreshSession(Certificate certificate, String source);
/**
* Return true if refreshing sessions is allowed
*
* @return true if refreshing sessions is allowed
*/
boolean isRefreshAllowed();
/**
* Returns the {@link PrivilegeContext} for the given certificate
*

View File

@ -22,7 +22,6 @@ import java.io.OutputStream;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
@ -124,9 +123,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
*/
protected SecretKey secretKey;
/**
* flag if session refreshing is allowed
*/
protected boolean allowSessionRefresh;
protected PrivilegeConflictResolution privilegeConflictResolution;
private String identifier;
@Override
public boolean isRefreshAllowed() {
return this.allowSessionRefresh;
}
@Override
public EncryptionHandler getEncryptionHandler() throws PrivilegeException {
return this.encryptionHandler;
@ -1038,7 +1047,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
if (ctx.getUserRep().getUsername().equals(newUser.getUsername())) {
Certificate cert = ctx.getCertificate();
cert = buildCertificate(cert.getUsage(), newUser, cert.getAuthToken(), cert.getSessionId(),
cert.getSource(), cert.getLoginTime());
cert.getSource(), cert.getLoginTime(), cert.isKeepAlive());
PrivilegeContext privilegeContext = buildPrivilegeContext(cert, newUser);
this.privilegeContextMap.put(cert.getSessionId(), privilegeContext);
}
@ -1063,7 +1072,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Certificate cert = ctx.getCertificate();
cert = buildCertificate(cert.getUsage(), user, cert.getAuthToken(), cert.getSessionId(),
cert.getSource(), cert.getLoginTime());
cert.getSource(), cert.getLoginTime(), cert.isKeepAlive());
PrivilegeContext privilegeContext = buildPrivilegeContext(cert, user);
this.privilegeContextMap.put(cert.getSessionId(), privilegeContext);
}
@ -1128,7 +1137,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create a new certificate, with details of the user
Usage usage = userChallenge.getUsage();
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, userChallenge.getSource(),
new Date());
LocalDateTime.now(), false);
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
@ -1145,12 +1154,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
@Override
public Certificate authenticate(String username, char[] password) {
return authenticate(username, password, "unknown", Usage.ANY);
public Certificate authenticate(String username, char[] password, boolean keepAlive) {
return authenticate(username, password, "unknown", Usage.ANY, keepAlive);
}
@Override
public Certificate authenticate(String username, char[] password, String source, Usage usage) {
public Certificate authenticate(String username, char[] password, String source, Usage usage, boolean keepAlive) {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
try {
@ -1178,7 +1187,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String sessionId = UUID.randomUUID().toString();
// create a new certificate, with details of the user
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source, new Date());
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source, LocalDateTime.now(),
keepAlive);
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
@ -1204,12 +1214,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
@Override
public Certificate authenticateSingleSignOn(Object data) throws PrivilegeException {
return authenticateSingleSignOn(data, "unknown");
public Certificate authenticateSingleSignOn(Object data, boolean keepAlive) throws PrivilegeException {
return authenticateSingleSignOn(data, "unknown", keepAlive);
}
@Override
public Certificate authenticateSingleSignOn(Object data, String source) throws PrivilegeException {
public Certificate authenticateSingleSignOn(Object data, String source, boolean keepAlive)
throws PrivilegeException {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
if (this.ssoHandler == null)
throw new IllegalStateException("The SSO Handler is not configured!");
@ -1234,7 +1245,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String sessionId = UUID.randomUUID().toString();
// create a new certificate, with details of the user
Certificate certificate = buildCertificate(Usage.ANY, user, authToken, sessionId, source, new Date());
Certificate certificate = buildCertificate(Usage.ANY, user, authToken, sessionId, source, LocalDateTime.now(),
keepAlive);
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
@ -1247,12 +1259,69 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
return certificate;
}
@Override
public Certificate refresh(Certificate certificate, String source) throws AccessDeniedException {
DBC.PRE.assertNotNull("certificate must not be null!", certificate);
try {
// username must be at least 2 characters in length
if (!this.allowSessionRefresh)
throw new AccessDeniedException("Refreshing of sessions not allowed!");
validate(certificate);
if (!certificate.isKeepAlive())
throw new AccessDeniedException("Refreshing of session not allowed!");
if (!certificate.getSource().equals(source)) {
logger.error("Source of existing session {} is not the same as the refresh request's source {}",
certificate.getSource(), source);
}
// check the password
User user = this.persistenceHandler.getUser(certificate.getUsername());
// get 2 auth tokens
String authToken = this.encryptionHandler.nextToken();
// get next session id
String sessionId = UUID.randomUUID().toString();
// create a new certificate, with details of the user
Certificate refreshedCert = buildCertificate(certificate.getUsage(), user, authToken, sessionId, source,
LocalDateTime.now(), true);
PrivilegeContext privilegeContext = buildPrivilegeContext(refreshedCert, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
// invalidate the previous session
invalidate(certificate);
persistSessions();
// log
logger.info(MessageFormat
.format("User {0} refreshed session: {1}", user.getUsername(), refreshedCert)); //$NON-NLS-1$
// return the certificate
return refreshedCert;
} catch (PrivilegeException e) {
throw e;
} catch (RuntimeException e) {
logger.error(e.getMessage(), e);
String msg = "User {0} failed to refresh session: {1}"; //$NON-NLS-1$
msg = MessageFormat.format(msg, certificate.getUsername(), e.getMessage());
throw new PrivilegeException(msg, e);
}
}
private Certificate buildCertificate(Usage usage, User user, String authToken, String sessionId, String source,
Date loginTime) {
LocalDateTime loginTime, boolean keepAlive) {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
Set<String> userRoles = user.getRoles();
return new Certificate(usage, sessionId, user.getUsername(), user.getFirstname(), user.getLastname(),
user.getUserState(), authToken, source, loginTime, user.getLocale(), userRoles,
user.getUserState(), authToken, source, loginTime, keepAlive, user.getLocale(), userRoles,
new HashMap<>(user.getProperties()));
}
@ -1341,7 +1410,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
// create a new certificate, with details of the user
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source, stub.getLoginTime());
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source, stub.getLoginTime(),
stub.isKeepAlive());
certificate.setLocale(stub.getLocale());
certificate.setLastAccess(stub.getLastAccess());
@ -1538,7 +1608,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
PrivilegeContext privilegeContext = this.privilegeContextMap.remove(certificate.getSessionId());
// persist sessions
persistSessions();
if (privilegeContext != null)
persistSessions();
// return true if object was really removed
boolean loggedOut = privilegeContext != null;
@ -1592,7 +1663,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new PrivilegeException(msg);
}
certificate.setLastAccess(new Date());
certificate.setLastAccess(LocalDateTime.now());
if (!certificate.getSource().equals(this.identifier))
throw new IllegalStateException(
@ -1624,15 +1695,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// validate that challenge certificate is not expired (1 hour only)
if (sessionCertificate.getUsage() != Usage.ANY) {
LocalDateTime dateTime = LocalDateTime
.ofInstant(sessionCertificate.getLoginTime().toInstant(), ZoneId.systemDefault());
LocalDateTime dateTime = sessionCertificate.getLoginTime();
if (dateTime.plusHours(1).isBefore(LocalDateTime.now())) {
invalidate(sessionCertificate);
throw new NotAuthenticatedException("Certificate has already expired!"); //$NON-NLS-1$
}
}
certificate.setLastAccess(new Date());
certificate.setLastAccess(LocalDateTime.now());
// TODO decide if we want to assert source did not change!
if (!source.equals(SOURCE_UNKNOWN) && !certificate.getSource().equals(source)) {
@ -1728,6 +1798,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
handleConflictResolutionParam(parameterMap);
handleSecretParams(parameterMap);
this.allowSessionRefresh = Boolean.parseBoolean(parameterMap.get(PARAM_ALLOW_SESSION_REFRESH));
// validate policies on privileges of Roles
for (Role role : persistenceHandler.getAllRoles()) {
validatePolicies(role);
@ -1938,11 +2010,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
* the char array containing the passwort which is to be set to zeroes
*/
private void clearPassword(char[] password) {
if (password != null) {
for (int i = 0; i < password.length; i++) {
password[i] = 0;
}
}
if (password != null)
Arrays.fill(password, (char) 0);
}
@Override
@ -2054,7 +2123,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create a new certificate, with details of the user
Certificate systemUserCertificate = buildCertificate(Usage.ANY, user, authToken, sessionId, this.identifier,
new Date());
LocalDateTime.now(), false);
// create and save a new privilege context
PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user);

View File

@ -166,6 +166,11 @@ public interface PrivilegeHandler {
*/
String PARAM_SECRET_KEY = "secretKey"; //$NON-NLS-1$
/**
* configuration parameter to define if session refreshing is allowed
*/
String PARAM_ALLOW_SESSION_REFRESH = "allowSessionRefresh"; //$NON-NLS-1$
/**
* configuration parameter to define a secret salt
*/
@ -608,13 +613,15 @@ public interface PrivilegeHandler {
* @param password
* the password with which this user is to be authenticated. Null passwords are not accepted and they must meet
* the requirements of the {@link #validatePassword(char[])}-method
* @param keepAlive
* should this session be kept alive
*
* @return a {@link Certificate} with which this user may then perform actions
*
* @throws AccessDeniedException
* if the user credentials are not valid
*/
Certificate authenticate(String username, char[] password) throws AccessDeniedException;
Certificate authenticate(String username, char[] password, boolean keepAlive) throws AccessDeniedException;
/**
* Authenticates a user by validating that a {@link User} for the given username and password exist and then returns
@ -629,26 +636,31 @@ public interface PrivilegeHandler {
* the source of the authentication request, i.e. remote IP
* @param usage
* the usage type for this authentication
* @param keepAlive
* should this session be kept alive
*
* @return a {@link Certificate} with which this user may then perform actions
*
* @throws AccessDeniedException
* if the user credentials are not valid
*/
Certificate authenticate(String username, char[] password, String source, Usage usage) throws AccessDeniedException;
Certificate authenticate(String username, char[] password, String source, Usage usage, boolean keepAlive)
throws AccessDeniedException;
/**
* Authenticates a user on a remote Single Sign On service. This is implemented by the
*
* @param data
* the data to perform the SSO
* @param keepAlive
* should this session be kept alive
*
* @return the {@link Certificate} for the user
*
* @throws PrivilegeException
* if something goes wrong with the SSO
*/
Certificate authenticateSingleSignOn(Object data) throws PrivilegeException;
Certificate authenticateSingleSignOn(Object data, boolean keepAlive) throws PrivilegeException;
/**
* Authenticates a user on a remote Single Sign On service. This is implemented by the
@ -657,13 +669,37 @@ public interface PrivilegeHandler {
* the data to perform the SSO
* @param source
* the source of the SSO authentication
* @param keepAlive
* may the certificate be kept alive
*
* @return the {@link Certificate} for the user
*
* @throws PrivilegeException
* if something goes wrong with the SSO
*/
Certificate authenticateSingleSignOn(Object data, String source) throws PrivilegeException;
Certificate authenticateSingleSignOn(Object data, String source, boolean keepAlive) throws PrivilegeException;
/**
* Refreshes the given certificate's session with a new session, i.e. a new certificate
*
* @param certificate
* the certificate for which to perform a refresh
* @param source
* the source of the refresh request
*
* @return a {@link Certificate} with which this user may then perform actions
*
* @throws AccessDeniedException
* if the certificate is now valid, or refreshing is not allowed
*/
Certificate refresh(Certificate certificate, String source) throws AccessDeniedException;
/**
* Return true if refreshing sessions is allowed
*
* @return true if refreshing sessions is allowed
*/
boolean isRefreshAllowed();
/**
* Invalidates the session for the given {@link Certificate}, effectively logging out the user who was authenticated

View File

@ -183,6 +183,11 @@ public class XmlConstants {
*/
public static final String XML_ATTR_LOGIN_TIME = "loginTime";
/**
* XML_ATTR_KEEP_ALIVE = "keepAlive" :
*/
public static final String XML_ATTR_KEEP_ALIVE = "keepAlive";
/**
* XML_ATTR_LAST_ACCESS = "lastAccess" :
*/

View File

@ -18,7 +18,11 @@ package li.strolch.privilege.model;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import java.io.Serializable;
import java.util.*;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import li.strolch.privilege.base.PrivilegeConstants;
import li.strolch.privilege.base.PrivilegeException;
@ -29,7 +33,7 @@ import li.strolch.utils.helper.StringHelper;
/**
* The {@link Certificate} is the object a client keeps when accessing a Privilege enabled system. This object is the
* instance which is always used when performing an access and is returned when a user performs a login through {@link
* PrivilegeHandler#authenticate(String, char[])}
* PrivilegeHandler#authenticate(String, char[], boolean)}
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@ -43,13 +47,14 @@ public final class Certificate implements Serializable {
private final UserState userState;
private final String authToken;
private final String source;
private final Date loginTime;
private final LocalDateTime loginTime;
private final boolean keepAlive;
private final Set<String> userRoles;
private final Map<String, String> propertyMap;
private Locale locale;
private Date lastAccess;
private LocalDateTime lastAccess;
/**
* Default constructor initializing with all information needed for this certificate
@ -65,9 +70,9 @@ public final class Certificate implements Serializable {
* the users session id
* @param username
* the users login name
* @param firstname
* @param firstName
* the users first name
* @param lastname
* @param lastName
* the users last name
* @param authToken
* the authentication token defining the users unique session and is a private field of this certificate.
@ -79,9 +84,9 @@ public final class Certificate implements Serializable {
* a {@link Map} containing string value pairs of properties for the logged in user. These properties can be
* edited and can be used for the user to change settings of this session
*/
public Certificate(Usage usage, String sessionId, String username, String firstname, String lastname,
UserState userState, String authToken, String source, Date loginTime, Locale locale, Set<String> userRoles,
Map<String, String> propertyMap) {
public Certificate(Usage usage, String sessionId, String username, String firstName, String lastName,
UserState userState, String authToken, String source, LocalDateTime loginTime, boolean keepAlive,
Locale locale, Set<String> userRoles, Map<String, String> propertyMap) {
// validate arguments are not null
if (StringHelper.isEmpty(sessionId)) {
@ -106,12 +111,13 @@ public final class Certificate implements Serializable {
this.usage = usage;
this.sessionId = sessionId;
this.username = username;
this.firstname = firstname;
this.lastname = lastname;
this.firstname = firstName;
this.lastname = lastName;
this.userState = userState;
this.authToken = authToken;
this.source = source;
this.loginTime = loginTime;
this.keepAlive = keepAlive;
// if no locale is given, set default
if (locale == null)
@ -125,7 +131,7 @@ public final class Certificate implements Serializable {
this.propertyMap = Collections.unmodifiableMap(propertyMap);
this.userRoles = Collections.unmodifiableSet(userRoles);
this.lastAccess = new Date();
this.lastAccess = LocalDateTime.now();
}
public Usage getUsage() {
@ -224,10 +230,14 @@ public final class Certificate implements Serializable {
return userState;
}
public Date getLoginTime() {
public LocalDateTime getLoginTime() {
return this.loginTime;
}
public boolean isKeepAlive() {
return this.keepAlive;
}
public String getAuthToken() {
return this.authToken;
}
@ -236,11 +246,11 @@ public final class Certificate implements Serializable {
return this.source;
}
public Date getLastAccess() {
public LocalDateTime getLastAccess() {
return this.lastAccess;
}
public void setLastAccess(Date lastAccess) {
public void setLastAccess(LocalDateTime lastAccess) {
this.lastAccess = lastAccess;
}

View File

@ -9,8 +9,8 @@ public class UserChallenge {
private final String challenge;
private final String source;
private final LocalDateTime initiated;
private final Usage usage;
private boolean fulfilled;
private Usage usage;
public UserChallenge(Usage usage, User user, String challenge, String source) {
this.usage = usage;

View File

@ -15,13 +15,15 @@
*/
package li.strolch.privilege.xml;
import static java.util.Comparator.comparing;
import static li.strolch.privilege.helper.XmlConstants.*;
import java.io.OutputStream;
import java.util.List;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.privilege.model.Certificate;
import li.strolch.utils.helper.XmlHelper;
import li.strolch.utils.iso8601.ISO8601FormatFactory;
import li.strolch.utils.iso8601.ISO8601;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -30,8 +32,8 @@ import org.w3c.dom.Element;
*/
public class CertificateStubsDomWriter {
private List<Certificate> certificates;
private OutputStream outputStream;
private final List<Certificate> certificates;
private final OutputStream outputStream;
public CertificateStubsDomWriter(List<Certificate> certificates, OutputStream outputStream) {
this.certificates = certificates;
@ -42,40 +44,41 @@ public class CertificateStubsDomWriter {
// create document root
Document doc = XmlHelper.createDocument();
Element rootElement = doc.createElement(XmlConstants.XML_ROOT_CERTIFICATES);
Element rootElement = doc.createElement(XML_ROOT_CERTIFICATES);
doc.appendChild(rootElement);
this.certificates.stream().sorted((c1, c2) -> c1.getSessionId().compareTo(c2.getSessionId())).forEach(cert -> {
this.certificates.stream().sorted(comparing(Certificate::getSessionId)).forEach(cert -> {
// create the certificate element
Element certElement = doc.createElement(XmlConstants.XML_CERTIFICATE);
Element certElement = doc.createElement(XML_CERTIFICATE);
rootElement.appendChild(certElement);
// sessionId;
certElement.setAttribute(XmlConstants.XML_ATTR_SESSION_ID, cert.getSessionId());
certElement.setAttribute(XML_ATTR_SESSION_ID, cert.getSessionId());
// usage;
certElement.setAttribute(XmlConstants.XML_ATTR_USAGE, cert.getUsage().name());
certElement.setAttribute(XML_ATTR_USAGE, cert.getUsage().name());
// username;
certElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, cert.getUsername());
certElement.setAttribute(XML_ATTR_USERNAME, cert.getUsername());
// authToken;
certElement.setAttribute(XmlConstants.XML_ATTR_AUTH_TOKEN, cert.getAuthToken());
certElement.setAttribute(XML_ATTR_AUTH_TOKEN, cert.getAuthToken());
// source;
certElement.setAttribute(XmlConstants.XML_ATTR_SOURCE, cert.getSource());
certElement.setAttribute(XML_ATTR_SOURCE, cert.getSource());
// locale;
certElement.setAttribute(XmlConstants.XML_ATTR_LOCALE, cert.getLocale().toLanguageTag());
certElement.setAttribute(XML_ATTR_LOCALE, cert.getLocale().toLanguageTag());
// loginTime;
certElement.setAttribute(XmlConstants.XML_ATTR_LOGIN_TIME,
ISO8601FormatFactory.getInstance().formatDate(cert.getLoginTime()));
certElement.setAttribute(XML_ATTR_LOGIN_TIME, ISO8601.toString(cert.getLoginTime()));
// lastAccess;
certElement.setAttribute(XmlConstants.XML_ATTR_LAST_ACCESS,
ISO8601FormatFactory.getInstance().formatDate(cert.getLastAccess()));
certElement.setAttribute(XML_ATTR_LAST_ACCESS, ISO8601.toString(cert.getLastAccess()));
// keepAlive;
certElement.setAttribute(XML_ATTR_KEEP_ALIVE, String.valueOf(cert.isKeepAlive()));
});
// write the container file to disk

View File

@ -16,20 +16,20 @@
package li.strolch.privilege.xml;
import static li.strolch.privilege.handler.DefaultPrivilegeHandler.SOURCE_UNKNOWN;
import static li.strolch.privilege.helper.XmlConstants.*;
import static li.strolch.utils.helper.StringHelper.isEmpty;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.privilege.model.Usage;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.XmlHelper;
import li.strolch.utils.iso8601.ISO8601FormatFactory;
import li.strolch.utils.iso8601.ISO8601;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
@ -39,7 +39,7 @@ import org.xml.sax.helpers.DefaultHandler;
*/
public class CertificateStubsSaxReader extends DefaultHandler {
private InputStream inputStream;
private final InputStream inputStream;
private List<CertificateStub> stubs;
public CertificateStubsSaxReader(InputStream inputStream) {
@ -56,21 +56,20 @@ public class CertificateStubsSaxReader extends DefaultHandler {
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
switch (qName) {
case XmlConstants.XML_ROOT_CERTIFICATES:
case XML_ROOT_CERTIFICATES:
break;
case XmlConstants.XML_CERTIFICATE:
case XML_CERTIFICATE:
CertificateStub stub = new CertificateStub();
stub.usage = Usage.valueOf(attributes.getValue(XmlConstants.XML_ATTR_USAGE));
stub.sessionId = attributes.getValue(XmlConstants.XML_ATTR_SESSION_ID);
stub.username = attributes.getValue(XmlConstants.XML_ATTR_USERNAME);
stub.authToken = attributes.getValue(XmlConstants.XML_ATTR_AUTH_TOKEN);
stub.source = attributes.getValue(XmlConstants.XML_ATTR_SOURCE);
stub.locale = Locale.forLanguageTag(attributes.getValue(XmlConstants.XML_ATTR_LOCALE));
stub.loginTime = ISO8601FormatFactory.getInstance()
.parseDate(attributes.getValue(XmlConstants.XML_ATTR_LOGIN_TIME));
stub.lastAccess = ISO8601FormatFactory.getInstance()
.parseDate(attributes.getValue(XmlConstants.XML_ATTR_LAST_ACCESS));
stub.usage = Usage.valueOf(attributes.getValue(XML_ATTR_USAGE));
stub.sessionId = attributes.getValue(XML_ATTR_SESSION_ID);
stub.username = attributes.getValue(XML_ATTR_USERNAME);
stub.authToken = attributes.getValue(XML_ATTR_AUTH_TOKEN);
stub.source = attributes.getValue(XML_ATTR_SOURCE);
stub.locale = Locale.forLanguageTag(attributes.getValue(XML_ATTR_LOCALE));
stub.loginTime = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LOGIN_TIME)).toLocalDateTime();
stub.lastAccess = ISO8601.parseToZdt(attributes.getValue(XML_ATTR_LAST_ACCESS)).toLocalDateTime();
stub.keepAlive = Boolean.parseBoolean(attributes.getValue(XML_ATTR_KEEP_ALIVE));
DBC.INTERIM.assertNotEmpty("sessionId missing on sessions data!", stub.sessionId);
DBC.INTERIM.assertNotEmpty("username missing on sessions data!", stub.username);
@ -87,15 +86,16 @@ public class CertificateStubsSaxReader extends DefaultHandler {
}
}
public class CertificateStub {
public static class CertificateStub {
private Usage usage;
private String sessionId;
private String username;
private String authToken;
private String source;
private Locale locale;
private Date loginTime;
private Date lastAccess;
private LocalDateTime loginTime;
private LocalDateTime lastAccess;
private boolean keepAlive;
public Usage getUsage() {
return this.usage;
@ -121,12 +121,16 @@ public class CertificateStubsSaxReader extends DefaultHandler {
return locale;
}
public Date getLoginTime() {
public LocalDateTime getLoginTime() {
return loginTime;
}
public Date getLastAccess() {
public LocalDateTime getLastAccess() {
return lastAccess;
}
public boolean isKeepAlive() {
return this.keepAlive;
}
}
}

View File

@ -22,7 +22,7 @@ public class AbstractPrivilegeTest {
protected PrivilegeContext ctx;
protected void login(String username, char[] password) {
Certificate certificate = privilegeHandler.authenticate(username, password);
Certificate certificate = privilegeHandler.authenticate(username, password, false);
assertTrue("Certificate is null!", certificate != null);
this.ctx = privilegeHandler.validate(certificate);
}

View File

@ -390,7 +390,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest {
}
// change password back
certificate = this.privilegeHandler.authenticate(ADMIN, PASS_TED);
certificate = this.privilegeHandler.authenticate(ADMIN, PASS_TED, false);
this.privilegeHandler.setUserPassword(certificate, ADMIN, ArraysHelper.copyOf(PASS_ADMIN));
this.privilegeHandler.invalidate(certificate);
}
@ -742,7 +742,7 @@ public class PrivilegeTest extends AbstractPrivilegeTest {
try {
// testFailAuthAsBob
// Will fail because user bob is not yet enabled
this.privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB));
this.privilegeHandler.authenticate(BOB, ArraysHelper.copyOf(PASS_BOB), false);
fail("User Bob may not authenticate because the user is not yet enabled!");
} catch (PrivilegeException e) {
String msg = "User bob does not have state ENABLED and can not login!";

View File

@ -42,7 +42,7 @@ public class SsoHandlerTest extends AbstractPrivilegeTest {
data.put("roles", "PrivilegeAdmin, AppUser");
// auth
Certificate cert = this.privilegeHandler.authenticateSingleSignOn(data);
Certificate cert = this.privilegeHandler.authenticateSingleSignOn(data, false);
this.ctx = this.privilegeHandler.validate(cert);
// validate action

View File

@ -19,9 +19,7 @@ import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIV
import static li.strolch.runtime.StrolchConstants.StrolchPrivilegeConstants.PRIVILEGE_INVALIDATE_SESSION;
import java.text.MessageFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.Future;
@ -51,13 +49,15 @@ import org.slf4j.LoggerFactory;
public class DefaultStrolchSessionHandler extends StrolchComponent implements StrolchSessionHandler {
public static final String PARAM_SESSION_TTL_MINUTES = "session.ttl.minutes"; //$NON-NLS-1$
public static final String PARAM_SESSION_MAX_KEEP_ALIVE_MINUTES = "session.maxKeepAlive.minutes"; //$NON-NLS-1$
public static final String PARAM_SESSION_RELOAD_SESSIONS = "session.reload"; //$NON-NLS-1$
private static final Logger logger = LoggerFactory.getLogger(DefaultStrolchSessionHandler.class);
private PrivilegeHandler privilegeHandler;
private Map<String, Certificate> certificateMap;
private boolean reloadSessions;
private long sessionTtl;
private int sessionTtlMinutes;
private int maxKeepAliveMinutes;
private ScheduledFuture<?> validateSessionsTask;
private Future<?> persistSessionsTask;
@ -66,9 +66,26 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
super(container, componentName);
}
@Override
public int getSessionTtlMinutes() {
return this.sessionTtlMinutes;
}
@Override
public int getSessionMaxKeepAliveMinutes() {
return this.maxKeepAliveMinutes;
}
@Override
public boolean isRefreshAllowed() {
return this.privilegeHandler.isRefreshAllowed();
}
@Override
public void initialize(ComponentConfiguration configuration) throws Exception {
this.sessionTtl = TimeUnit.MINUTES.toMillis(configuration.getInt(PARAM_SESSION_TTL_MINUTES, 30));
this.sessionTtlMinutes = configuration.getInt(PARAM_SESSION_TTL_MINUTES, 30);
this.maxKeepAliveMinutes = configuration
.getInt(PARAM_SESSION_MAX_KEEP_ALIVE_MINUTES, Math.max(this.sessionTtlMinutes, 30));
this.reloadSessions = configuration.getBoolean(PARAM_SESSION_RELOAD_SESSIONS, false);
super.initialize(configuration);
}
@ -133,24 +150,11 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
}
@Override
public Certificate authenticate(String username, char[] password) {
public Certificate authenticate(String username, char[] password, String source, Usage usage, boolean keepAlive) {
DBC.PRE.assertNotEmpty("Username must be set!", username); //$NON-NLS-1$
DBC.PRE.assertNotNull("Passwort must be set", password); //$NON-NLS-1$
Certificate certificate = this.privilegeHandler.authenticate(username, password);
this.certificateMap.put(certificate.getAuthToken(), certificate);
logger.info(MessageFormat.format("{0} sessions currently active.", this.certificateMap.size())); //$NON-NLS-1$
return certificate;
}
@Override
public Certificate authenticate(String username, char[] password, String source, Usage usage) {
DBC.PRE.assertNotEmpty("Username must be set!", username); //$NON-NLS-1$
DBC.PRE.assertNotNull("Passwort must be set", password); //$NON-NLS-1$
Certificate certificate = this.privilegeHandler.authenticate(username, password, source, usage);
Certificate certificate = this.privilegeHandler.authenticate(username, password, source, usage, keepAlive);
this.certificateMap.put(certificate.getAuthToken(), certificate);
logger.info(MessageFormat.format("{0} sessions currently active.", this.certificateMap.size())); //$NON-NLS-1$
@ -178,6 +182,17 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
return certificate;
}
@Override
public Certificate refreshSession(Certificate certificate, String source) {
Certificate refreshedSession = this.privilegeHandler.refreshSession(certificate, source);
invalidate(certificate);
this.certificateMap.put(refreshedSession.getAuthToken(), refreshedSession);
logger.info(MessageFormat.format("{0} sessions currently active.", this.certificateMap.size())); //$NON-NLS-1$
return refreshedSession;
}
@Override
public Certificate validate(String authToken) throws StrolchNotAuthenticatedException {
DBC.PRE.assertNotEmpty("authToken must be set!", authToken); //$NON-NLS-1$
@ -298,15 +313,24 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
certificateMap = new HashMap<>(this.certificateMap);
}
LocalDateTime timeOutTime = LocalDateTime.now().minus(sessionTtl, ChronoUnit.MILLIS);
ZoneId systemDefault = ZoneId.systemDefault();
LocalDateTime maxKeepAliveTime = LocalDateTime.now().minus(this.maxKeepAliveMinutes, ChronoUnit.MINUTES);
LocalDateTime timeOutTime = LocalDateTime.now().minus(this.sessionTtlMinutes, ChronoUnit.MINUTES);
for (Certificate certificate : certificateMap.values()) {
Instant lastAccess = certificate.getLastAccess().toInstant();
if (timeOutTime.isAfter(LocalDateTime.ofInstant(lastAccess, systemDefault))) {
String msg = "Session {0} for user {1} has expired, invalidating session..."; //$NON-NLS-1$
logger.info(MessageFormat.format(msg, certificate.getSessionId(), certificate.getUsername()));
sessionTimeout(certificate);
if (certificate.isKeepAlive()) {
if (maxKeepAliveTime.isAfter(certificate.getLoginTime())) {
String msg = "KeepAlive for session {0} for user {1} has expired, invalidating session..."; //$NON-NLS-1$
logger.info(MessageFormat.format(msg, certificate.getSessionId(), certificate.getUsername()));
sessionTimeout(certificate);
}
} else {
if (timeOutTime.isAfter(certificate.getLastAccess())) {
String msg = "Session {0} for user {1} has expired, invalidating session..."; //$NON-NLS-1$
logger.info(MessageFormat.format(msg, certificate.getSessionId(), certificate.getUsername()));
sessionTimeout(certificate);
}
}
}
}

View File

@ -25,6 +25,7 @@ public class StrolchRestfulConstants {
public static final String STROLCH_CERTIFICATE = "strolch.certificate"; //$NON-NLS-1$
public static final String STROLCH_REQUEST_SOURCE= "strolch.requestSource"; //$NON-NLS-1$
public static final String STROLCH_AUTHORIZATION = "strolch.authorization"; //$NON-NLS-1$
public static final String STROLCH_AUTHORIZATION_EXPIRATION_DATE = "strolch.authorization.expirationDate"; //$NON-NLS-1$
public static final String MSG = "msg";
public static final String EXCEPTION_MSG = "exceptionMsg";

View File

@ -35,16 +35,25 @@ import li.strolch.rest.model.UserSession;
public interface StrolchSessionHandler {
/**
* Authenticates a user with the given credentials
* Returns the time to live for a session in minutes
*
* @param username
* the username
* @param password
* the password
*
* @return the {@link Certificate} for the logged in user
* @return the time to live for a session in minutes
*/
Certificate authenticate(String username, char[] password);
int getSessionTtlMinutes();
/**
* Returns the max keep alive for a session in minutes
*
* @return the max keep alive for a session in minutes
*/
int getSessionMaxKeepAliveMinutes();
/**
* Return true if refreshing sessions is allowed
*
* @return true if refreshing sessions is allowed
*/
boolean isRefreshAllowed();
/**
* Authenticates a user with the given credentials
@ -57,10 +66,12 @@ public interface StrolchSessionHandler {
* the source of the request
* @param usage
* the usage for this authentication
* @param keepAlive
* should the session have a keepAlive
*
* @return the {@link Certificate} for the logged in user
*/
Certificate authenticate(String username, char[] password, String source, Usage usage);
Certificate authenticate(String username, char[] password, String source, Usage usage, boolean keepAlive);
/**
* Performs a single-sign-on with the given data, if SSO is enabled
@ -84,6 +95,18 @@ public interface StrolchSessionHandler {
*/
Certificate authenticateSingleSignOn(Object data, String source);
/**
* Performs a refresh of the given certificate's session by returning a new certificate
*
* @param certificate
* the certificate to refresh
* @param source
* the source of the request
*
* @return certificate a new certificate
*/
Certificate refreshSession(Certificate certificate, String source);
/**
* Validates that a {@link Certificate} exists with the given auth token and is still valid
*

View File

@ -15,6 +15,8 @@
*/
package li.strolch.rest.endpoint;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_AUTHORIZATION;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_AUTHORIZATION_EXPIRATION_DATE;
import static li.strolch.rest.filters.AuthenticationRequestFilter.getRemoteIp;
import javax.servlet.http.HttpServletRequest;
@ -22,6 +24,7 @@ import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@ -36,11 +39,11 @@ import li.strolch.privilege.model.IPrivilege;
import li.strolch.privilege.model.PrivilegeContext;
import li.strolch.privilege.model.Usage;
import li.strolch.rest.RestfulStrolchComponent;
import li.strolch.rest.StrolchRestfulConstants;
import li.strolch.rest.StrolchSessionHandler;
import li.strolch.rest.helper.ResponseUtil;
import li.strolch.runtime.privilege.PrivilegeHandler;
import li.strolch.utils.helper.ExceptionHelper;
import li.strolch.utils.iso8601.ISO8601;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -58,12 +61,12 @@ public class AuthenticationService {
public Response authenticate(@Context HttpServletRequest request, @Context HttpHeaders headers, String data) {
JsonObject login = new JsonParser().parse(data).getAsJsonObject();
JsonObject loginResult = new JsonObject();
try {
if (!login.has("username") || login.get("username").getAsString().length() < 2) {
logger.error("Authentication failed: Username was not given or is too short!");
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg", MessageFormat.format("Could not log in due to: {0}",
"Username was not given or is too short!")); //$NON-NLS-2$
return Response.status(Status.BAD_REQUEST).entity(loginResult.toString()).build();
@ -71,6 +74,7 @@ public class AuthenticationService {
if (!login.has("password") || login.get("password").getAsString().length() < 3) {
logger.error("Authentication failed: Password was not given or is too short!");
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg", MessageFormat.format("Could not log in due to: {0}",
"Password was not given or is too short!")); //$NON-NLS-2$
return Response.status(Status.BAD_REQUEST).entity(loginResult.toString()).build();
@ -78,12 +82,14 @@ public class AuthenticationService {
String username = login.get("username").getAsString();
String passwordEncoded = login.get("password").getAsString();
boolean keepAlive = login.has("keepAlive") && login.get("keepAlive").getAsBoolean();
byte[] decode = Base64.getDecoder().decode(passwordEncoded);
char[] password = new String(decode).toCharArray();
if (password.length < 3) {
logger.error("Authentication failed: Password was not given or is too short!");
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg", MessageFormat.format("Could not log in due to: {0}",
"Password was not given or is too short!")); //$NON-NLS-2$
return Response.status(Status.BAD_REQUEST).entity(loginResult.toString()).build();
@ -91,27 +97,31 @@ public class AuthenticationService {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
String source = getRemoteIp(request);
Certificate certificate = sessionHandler.authenticate(username, password, source, Usage.ANY);
Certificate certificate = sessionHandler.authenticate(username, password, source, Usage.ANY, keepAlive);
return getAuthenticationResponse(request, loginResult, certificate, source);
return getAuthenticationResponse(request, certificate, source, true);
} catch (InvalidCredentialsException e) {
logger.error("Authentication failed due to: " + e.getMessage());
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg", "Could not log in as the given credentials are invalid"); //$NON-NLS-1$
return Response.status(Status.UNAUTHORIZED).entity(loginResult.toString()).build();
} catch (AccessDeniedException e) {
logger.error("Authentication failed due to: " + e.getMessage());
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg",
MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-2$
return Response.status(Status.UNAUTHORIZED).entity(loginResult.toString()).build();
} catch (StrolchException | PrivilegeException e) {
logger.error(e.getMessage(), e);
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg",
MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-2$
return Response.status(Status.FORBIDDEN).entity(loginResult.toString()).build();
} catch (Exception e) {
logger.error(e.getMessage(), e);
String msg = e.getMessage();
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg", MessageFormat.format("{0}: {1}", e.getClass().getName(), msg)); //$NON-NLS-1$
return Response.serverError().entity(loginResult.toString()).build();
}
@ -122,33 +132,35 @@ public class AuthenticationService {
@Path("sso")
public Response authenticateSingleSignOn(@Context HttpServletRequest request, @Context HttpHeaders headers) {
JsonObject loginResult = new JsonObject();
try {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
String source = getRemoteIp(request);
Certificate certificate = sessionHandler.authenticateSingleSignOn(request.getUserPrincipal(), source);
return getAuthenticationResponse(request, loginResult, certificate, source);
return getAuthenticationResponse(request, certificate, source, true);
} catch (InvalidCredentialsException e) {
logger.error("Authentication failed due to: " + e.getMessage());
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg", "Could not log in as the given credentials are invalid"); //$NON-NLS-1$
return Response.status(Status.UNAUTHORIZED).entity(loginResult.toString()).build();
} catch (AccessDeniedException e) {
logger.error("Authentication failed due to: " + e.getMessage());
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg",
MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-2$
return Response.status(Status.UNAUTHORIZED).entity(loginResult.toString()).build();
} catch (StrolchException | PrivilegeException e) {
logger.error(e.getMessage(), e);
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg",
MessageFormat.format("Could not log in due to: {0}", e.getMessage())); //$NON-NLS-2$
return Response.status(Status.FORBIDDEN).entity(loginResult.toString()).build();
} catch (Exception e) {
logger.error(e.getMessage(), e);
String msg = e.getMessage();
JsonObject loginResult = new JsonObject();
loginResult.addProperty("msg", MessageFormat.format("{0}: {1}", e.getClass().getName(), msg)); //$NON-NLS-1$
return Response.serverError().entity(loginResult.toString()).build();
}
@ -219,6 +231,68 @@ public class AuthenticationService {
}
}
@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{authToken}")
public Response getValidatedSession(@Context HttpServletRequest request, @PathParam("authToken") String authToken) {
try {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
String source = getRemoteIp(request);
Certificate certificate = sessionHandler.validate(authToken, source);
return getAuthenticationResponse(request, certificate, source, false);
} catch (StrolchException | PrivilegeException e) {
logger.error("Session validation failed: " + e.getMessage());
JsonObject root = new JsonObject();
root.addProperty("msg", MessageFormat.format("Session invalid: {0}", e.getMessage()));
String json = new Gson().toJson(root);
return Response.status(Status.UNAUTHORIZED).entity(json).build();
} catch (Exception e) {
logger.error(e.getMessage(), e);
String msg = e.getMessage();
JsonObject root = new JsonObject();
root.addProperty("msg", MessageFormat.format("Session invalid: {0}: {1}", e.getClass().getName(), msg));
String json = new Gson().toJson(root);
return Response.serverError().entity(json).build();
}
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{authToken}")
public Response refreshSession(@Context HttpServletRequest request, @PathParam("authToken") String authToken) {
try {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
String source = getRemoteIp(request);
Certificate certificate = sessionHandler.validate(authToken, source);
Certificate refreshedCert = sessionHandler.refreshSession(certificate, source);
return getAuthenticationResponse(request, refreshedCert, source, true);
} catch (StrolchException | PrivilegeException e) {
logger.error("Session validation failed: " + e.getMessage());
JsonObject root = new JsonObject();
root.addProperty("msg", MessageFormat.format("Session invalid: {0}", e.getMessage()));
String json = new Gson().toJson(root);
return Response.status(Status.UNAUTHORIZED).entity(json).build();
} catch (Exception e) {
logger.error(e.getMessage(), e);
String msg = e.getMessage();
JsonObject root = new JsonObject();
root.addProperty("msg", MessageFormat.format("Session invalid: {0}: {1}", e.getClass().getName(), msg));
String json = new Gson().toJson(root);
return Response.serverError().entity(json).build();
}
}
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("challenge")
@ -268,8 +342,8 @@ public class AuthenticationService {
logger.warn(msg);
}
NewCookie cookie = new NewCookie(StrolchRestfulConstants.STROLCH_AUTHORIZATION, certificate.getAuthToken(),
"/", null, "Authorization header", (int) TimeUnit.DAYS.toSeconds(1), secureCookie);
NewCookie cookie = new NewCookie(STROLCH_AUTHORIZATION, certificate.getAuthToken(), "/", null,
"Authorization header", (int) TimeUnit.DAYS.toSeconds(1), secureCookie);
return Response.ok().entity(jsonObject.toString())//
.header(HttpHeaders.AUTHORIZATION, certificate.getAuthToken()).cookie(cookie).build();
@ -283,17 +357,37 @@ public class AuthenticationService {
}
}
private Response getAuthenticationResponse(HttpServletRequest request, JsonObject loginResult,
Certificate certificate, String source) {
private Response getAuthenticationResponse(HttpServletRequest request, Certificate certificate, String source,
boolean setCookies) {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
int sessionMaxKeepAliveMinutes = sessionHandler.getSessionMaxKeepAliveMinutes();
int cookieMaxAge;
if (certificate.isKeepAlive()) {
cookieMaxAge = (int) TimeUnit.MINUTES.toSeconds(sessionMaxKeepAliveMinutes);
} else {
cookieMaxAge = RestfulStrolchComponent.getInstance().getCookieMaxAge();
}
LocalDateTime expirationDate = LocalDateTime.now().plusSeconds(cookieMaxAge);
String expirationDateS = ISO8601.toString(expirationDate);
JsonObject loginResult = new JsonObject();
PrivilegeHandler privilegeHandler = RestfulStrolchComponent.getInstance().getContainer().getPrivilegeHandler();
PrivilegeContext privilegeContext = privilegeHandler.validate(certificate, source);
loginResult.addProperty("sessionId", certificate.getSessionId());
loginResult.addProperty("authToken", certificate.getAuthToken());
String authToken = certificate.getAuthToken();
loginResult.addProperty("authToken", authToken);
loginResult.addProperty("username", certificate.getUsername());
loginResult.addProperty("firstname", certificate.getFirstname());
loginResult.addProperty("lastname", certificate.getLastname());
loginResult.addProperty("locale", certificate.getLocale().toLanguageTag());
loginResult.addProperty("keepAlive", certificate.isKeepAlive());
loginResult.addProperty("keepAliveMinutes", sessionMaxKeepAliveMinutes);
loginResult.addProperty("cookieMaxAge", cookieMaxAge);
loginResult.addProperty("authorizationExpiration", expirationDateS);
loginResult.addProperty("refreshAllowed", sessionHandler.isRefreshAllowed());
if (!certificate.getPropertyMap().isEmpty()) {
JsonObject propObj = new JsonObject();
@ -336,15 +430,24 @@ public class AuthenticationService {
}
boolean secureCookie = RestfulStrolchComponent.getInstance().isSecureCookie();
int cookieMaxAge = RestfulStrolchComponent.getInstance().getCookieMaxAge();
if (secureCookie && !request.getScheme().equals("https")) {
logger.error(
"Authorization cookie is secure, but connection is not secure! Cookie won't be passed to client!");
}
NewCookie cookie = new NewCookie(StrolchRestfulConstants.STROLCH_AUTHORIZATION, certificate.getAuthToken(), "/",
null, "Authorization header", cookieMaxAge, secureCookie);
return Response.ok().entity(loginResult.toString())//
.header(HttpHeaders.AUTHORIZATION, certificate.getAuthToken()).cookie(cookie).build();
if (setCookies) {
NewCookie authCookie = new NewCookie(STROLCH_AUTHORIZATION, authToken, "/", null, "Authorization header",
cookieMaxAge, secureCookie);
NewCookie authExpirationCookie = new NewCookie(STROLCH_AUTHORIZATION_EXPIRATION_DATE, expirationDateS, "/",
null, "Authorization Expiration Date", cookieMaxAge, secureCookie);
return Response.ok().entity(loginResult.toString()) //
.header(HttpHeaders.AUTHORIZATION, authToken) //
.cookie(authCookie) //
.cookie(authExpirationCookie) //
.build();
}
return Response.ok().entity(loginResult.toString()).header(HttpHeaders.AUTHORIZATION, authToken).build();
}
}

View File

@ -205,7 +205,7 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
logger.info("Performing basic auth for user " + parts[0] + "...");
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
Certificate certificate = sessionHandler.authenticate(parts[0], parts[1].toCharArray(), remoteIp, Usage.SINGLE);
Certificate certificate = sessionHandler.authenticate(parts[0], parts[1].toCharArray(), remoteIp, Usage.SINGLE, false);
requestContext.setProperty(STROLCH_CERTIFICATE, certificate);
requestContext.setProperty(STROLCH_REQUEST_SOURCE, remoteIp);

View File

@ -15,36 +15,38 @@
*/
package li.strolch.rest.model;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.Locale;
import java.util.Set;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import li.strolch.privilege.model.Certificate;
import li.strolch.utils.iso8601.ISO8601FormatFactory;
import li.strolch.utils.iso8601.ISO8601;
public class UserSession {
private String sessionId;
private Date loginTime;
private String username;
private String firstname;
private String lastname;
private String source;
private Set<String> userRoles;
private Locale locale;
private Date lastAccess;
private final boolean keepAlive;
private final String sessionId;
private final LocalDateTime loginTime;
private final String username;
private final String firstName;
private final String lastName;
private final String source;
private final Set<String> userRoles;
private final Locale locale;
private final LocalDateTime lastAccess;
public UserSession(Certificate certificate) {
this.sessionId = certificate.getSessionId();
this.loginTime = certificate.getLoginTime();
this.username = certificate.getUsername();
this.firstname = certificate.getFirstname();
this.lastname = certificate.getLastname();
this.firstName = certificate.getFirstname();
this.lastName = certificate.getLastname();
this.source = certificate.getSource();
this.userRoles = certificate.getUserRoles();
this.locale = certificate.getLocale();
this.keepAlive = certificate.isKeepAlive();
this.lastAccess = certificate.getLastAccess();
}
@ -52,7 +54,7 @@ public class UserSession {
return locale;
}
public Date getLastAccess() {
public LocalDateTime getLastAccess() {
return lastAccess;
}
@ -60,7 +62,7 @@ public class UserSession {
return sessionId;
}
public Date getLoginTime() {
public LocalDateTime getLoginTime() {
return loginTime;
}
@ -69,11 +71,11 @@ public class UserSession {
}
public String getFirstname() {
return firstname;
return firstName;
}
public String getLastname() {
return lastname;
return lastName;
}
public String getSource() {
@ -88,14 +90,15 @@ public class UserSession {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("sessionId", this.sessionId);
jsonObject.addProperty("loginTime", ISO8601FormatFactory.getInstance().formatDate(this.loginTime));
jsonObject.addProperty("loginTime", ISO8601.toString(this.loginTime));
jsonObject.addProperty("username", this.username);
jsonObject.addProperty("firstname", this.firstname);
jsonObject.addProperty("lastname", this.lastname);
jsonObject.addProperty("firstname", this.firstName);
jsonObject.addProperty("lastname", this.lastName);
jsonObject.addProperty("source", this.source);
jsonObject.addProperty("keepAlive", this.keepAlive);
jsonObject.addProperty("locale", this.locale.toString());
jsonObject.addProperty("lastAccess", ISO8601FormatFactory.getInstance().formatDate(this.lastAccess));
jsonObject.addProperty("lastAccess", ISO8601.toString(this.lastAccess));
JsonArray rolesJ = new JsonArray();
for (String role : this.userRoles) {

View File

@ -18,7 +18,7 @@ package li.strolch.service.test;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.HashSet;
import li.strolch.privilege.base.AccessDeniedException;
@ -49,16 +49,15 @@ public class ServiceTest extends AbstractServiceTest {
this.thrown.expect(PrivilegeException.class);
TestService testService = new TestService();
getServiceHandler().doService(
new Certificate(null, null, null, null, null, null, null, null, new Date(), null, new HashSet<>(),
null), testService);
new Certificate(null, null, null, null, null, null, null, null, LocalDateTime.now(), false, null,
new HashSet<>(), null), testService);
}
@Test
public void shouldFailInvalidCertificate2() {
TestService testService = new TestService();
Certificate badCert = new Certificate(Usage.ANY, "1", "bob", "Bob", "Brown", UserState.ENABLED, "dsdf", "asd",
//$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
new Date(), null, new HashSet<>(), null);
LocalDateTime.now(), false, null, new HashSet<>(), null);
ServiceResult svcResult = getServiceHandler().doService(badCert, testService);
assertThat(svcResult.getThrowable(), instanceOf(NotAuthenticatedException.class));
}