[Major] Added request source for REST calls

This commit is contained in:
Robert von Burg 2019-03-09 19:38:30 +01:00
parent ce219ece59
commit 04c5263eeb
20 changed files with 558 additions and 181 deletions

View File

@ -65,13 +65,14 @@ public class StrolchJobsHandler extends StrolchComponent {
return strolchJob;
}
public List<StrolchJob> getJobs(Certificate cert) {
getContainer().getPrivilegeHandler().validate(cert).assertHasPrivilege(StrolchJobsHandler.class.getName());
public List<StrolchJob> getJobs(Certificate cert, String source) {
getContainer().getPrivilegeHandler().validate(cert, source).assertHasPrivilege(StrolchJobsHandler.class.getName());
return new ArrayList<>(this.jobs.values());
}
public StrolchJob getJob(Certificate cert, String jobName) {
getContainer().getPrivilegeHandler().validate(cert).assertHasPrivilege(StrolchJobsHandler.class.getName());
public StrolchJob getJob(Certificate cert, String source, String jobName) {
getContainer().getPrivilegeHandler().validate(cert, source)
.assertHasPrivilege(StrolchJobsHandler.class.getName());
StrolchJob strolchJob = this.jobs.get(jobName);
if (strolchJob == null)
throw new IllegalArgumentException("Job " + jobName + " is not registered!");

View File

@ -122,14 +122,15 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
public Certificate authenticate(String username, char[] password) {
assertContainerStarted();
Certificate certificate = this.privilegeHandler.authenticate(username, password);
StrolchRealm realm = getContainer().getRealm(certificate);
try (StrolchTransaction tx = realm.openTx(certificate, StrolchPrivilegeConstants.LOGIN)) {
tx.setSuppressDoNothingLogging(true);
tx.setSuppressAudits(true);
Audit audit = tx.auditFrom(AccessType.CREATE, StrolchPrivilegeConstants.PRIVILEGE,
StrolchPrivilegeConstants.CERTIFICATE, username);
tx.getAuditTrail().add(tx, audit);
}
writeAudit(certificate, StrolchPrivilegeConstants.LOGIN, AccessType.CREATE, username);
return certificate;
}
@Override
public Certificate authenticate(String username, char[] password, String source) {
assertContainerStarted();
Certificate certificate = this.privilegeHandler.authenticate(username, password, source);
writeAudit(certificate, StrolchPrivilegeConstants.LOGIN, AccessType.CREATE, username);
return certificate;
}
@ -137,15 +138,28 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
public Certificate authenticateSingleSignOn(Object data) {
assertContainerStarted();
Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data);
writeAudit(certificate, StrolchPrivilegeConstants.LOGIN, AccessType.CREATE, certificate.getUsername());
return certificate;
}
@Override
public Certificate authenticateSingleSignOn(Object data, String source) {
assertContainerStarted();
Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data, source);
writeAudit(certificate, StrolchPrivilegeConstants.LOGIN, AccessType.CREATE, certificate.getUsername());
return certificate;
}
private void writeAudit(Certificate certificate, String login, AccessType accessType, String username) {
StrolchRealm realm = getContainer().getRealm(certificate);
try (StrolchTransaction tx = realm.openTx(certificate, StrolchPrivilegeConstants.LOGIN)) {
try (StrolchTransaction tx = realm.openTx(certificate, login)) {
tx.setSuppressDoNothingLogging(true);
tx.setSuppressAudits(true);
Audit audit = tx.auditFrom(AccessType.CREATE, StrolchPrivilegeConstants.PRIVILEGE,
StrolchPrivilegeConstants.CERTIFICATE, certificate.getUsername());
Audit audit = tx
.auditFrom(accessType, StrolchPrivilegeConstants.PRIVILEGE, StrolchPrivilegeConstants.CERTIFICATE,
username);
tx.getAuditTrail().add(tx, audit);
}
return certificate;
}
@Override
@ -153,17 +167,15 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
return this.privilegeHandler.validate(certificate);
}
@Override
public PrivilegeContext validate(Certificate certificate, String source) throws PrivilegeException {
return this.privilegeHandler.validate(certificate, source);
}
@Override
public boolean invalidate(Certificate certificate) {
boolean invalidateSession = this.privilegeHandler.invalidate(certificate);
StrolchRealm realm = getContainer().getRealm(certificate);
try (StrolchTransaction tx = realm.openTx(certificate, StrolchPrivilegeConstants.LOGOUT)) {
tx.setSuppressDoNothingLogging(true);
tx.setSuppressAudits(true);
Audit audit = tx.auditFrom(AccessType.DELETE, StrolchPrivilegeConstants.PRIVILEGE,
StrolchPrivilegeConstants.CERTIFICATE, certificate.getUsername());
tx.getAuditTrail().add(tx, audit);
}
writeAudit(certificate, StrolchPrivilegeConstants.LOGOUT, AccessType.DELETE, certificate.getUsername());
return invalidateSession;
}
@ -171,14 +183,8 @@ public class DefaultStrolchPrivilegeHandler extends StrolchComponent implements
public boolean sessionTimeout(Certificate certificate) {
assertStarted();
boolean invalidateSession = this.privilegeHandler.invalidate(certificate);
StrolchRealm realm = getContainer().getRealm(certificate);
try (StrolchTransaction tx = realm.openTx(certificate, StrolchPrivilegeConstants.SESSION_TIME_OUT)) {
tx.setSuppressDoNothingLogging(true);
tx.setSuppressAudits(true);
Audit audit = tx.auditFrom(AccessType.DELETE, StrolchPrivilegeConstants.PRIVILEGE,
StrolchPrivilegeConstants.CERTIFICATE, certificate.getUsername());
tx.getAuditTrail().add(tx, audit);
}
writeAudit(certificate, StrolchPrivilegeConstants.SESSION_TIME_OUT, AccessType.DELETE,
certificate.getUsername());
return invalidateSession;
}

View File

@ -43,6 +43,22 @@ public interface PrivilegeHandler {
*/
Certificate authenticate(String username, char[] password);
/**
* Authenticate a user
*
* @param username
* the username
* @param password
* the password
* @param source
* the source of the request
*
* @return the certificate
*
* @see li.strolch.privilege.handler.PrivilegeHandler#authenticate(String, char[])
*/
Certificate authenticate(String username, char[] password, String source);
/**
* Authenticates a user on a remote Single Sign On service. This is implemented by the
*
@ -56,6 +72,21 @@ public interface PrivilegeHandler {
*/
Certificate authenticateSingleSignOn(Object data) throws PrivilegeException;
/**
* Authenticates a user on a remote Single Sign On service. This is implemented by the
*
* @param data
* the data to perform the SSO
* @param source
* the source of the request
*
* @return the {@link Certificate} for the user
*
* @throws PrivilegeException
* if something goes wrong with the SSO
*/
Certificate authenticateSingleSignOn(Object data, String source) throws PrivilegeException;
/**
* Returns the {@link PrivilegeContext} for the given certificate
*
@ -70,6 +101,22 @@ public interface PrivilegeHandler {
*/
PrivilegeContext validate(Certificate certificate) throws PrivilegeException;
/**
* Returns the {@link PrivilegeContext} for the given certificate
*
* @param certificate
* the certificate
* @param source
* the source of the request
*
* @return the {@link PrivilegeContext} for the given certificate
*
* @throws PrivilegeException
* if the certificate is not valid anymore
* @see li.strolch.privilege.handler.PrivilegeHandler#validate(li.strolch.privilege.model.Certificate)
*/
PrivilegeContext validate(Certificate certificate, String source) throws PrivilegeException;
/**
* Invalidates the given certificate
*

View File

@ -66,6 +66,7 @@ import org.slf4j.LoggerFactory;
public class DefaultPrivilegeHandler implements PrivilegeHandler {
protected static final Logger logger = LoggerFactory.getLogger(DefaultPrivilegeHandler.class);
public static final String SOURCE_UNKNOWN = "unknown";
/**
* Map keeping a reference to all active sessions
@ -124,6 +125,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
protected SecretKey secretKey;
protected PrivilegeConflictResolution privilegeConflictResolution;
private String identifier;
@Override
public EncryptionHandler getEncryptionHandler() throws PrivilegeException {
@ -196,15 +198,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Stream<Role> rolesStream = this.persistenceHandler.getAllRoles().stream();
// validate access to each role
// TODO throwing and catching exception ain't cool
rolesStream = rolesStream.filter(role -> {
try {
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role)));
return true;
} catch (AccessDeniedException e) {
return false;
}
});
rolesStream = rolesStream
.filter(role -> prvCtx.hasPrivilege(new SimpleRestrictable(PRIVILEGE_GET_ROLE, new Tuple(null, role))));
return rolesStream.map(Role::asRoleRep).collect(Collectors.toList());
}
@ -219,15 +214,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Stream<User> usersStream = this.persistenceHandler.getAllUsers().stream();
// validate access to each user
// TODO throwing and catching exception ain't cool
usersStream = usersStream.filter(user -> {
try {
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user)));
return true;
} catch (AccessDeniedException e) {
return false;
}
});
usersStream = usersStream
.filter(user -> prvCtx.hasPrivilege(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))));
return usersStream.map(User::asUserRep).collect(Collectors.toList());
}
@ -252,12 +240,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
List<User> allUsers = this.persistenceHandler.getAllUsers();
for (User user : allUsers) {
// TODO throwing and catching exception ain't cool
try {
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user)));
} catch (AccessDeniedException e) {
if (!prvCtx.hasPrivilege(new SimpleRestrictable(PRIVILEGE_GET_USER, new Tuple(null, user))))
continue;
}
// selections
boolean userIdSelected;
@ -746,7 +730,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// get User
User existingUser = this.persistenceHandler.getUser(username);
if (existingUser == null) {
throw new PrivilegeModelException(MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$
throw new PrivilegeModelException(
MessageFormat.format("User {0} does not exist!", username)); //$NON-NLS-1$
}
byte[] passwordHash = null;
@ -1051,7 +1036,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
for (PrivilegeContext ctx : ctxs) {
if (ctx.getUserRep().getUsername().equals(newUser.getUsername())) {
Certificate cert = ctx.getCertificate();
cert = buildCertificate(cert.getUsage(), newUser, cert.getAuthToken(), cert.getSessionId());
cert = buildCertificate(cert.getUsage(), newUser, cert.getAuthToken(), cert.getSessionId(),
cert.getSource());
PrivilegeContext privilegeContext = buildPrivilegeContext(cert, newUser);
this.privilegeContextMap.put(cert.getSessionId(), privilegeContext);
}
@ -1075,7 +1061,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
continue;
Certificate cert = ctx.getCertificate();
cert = buildCertificate(cert.getUsage(), user, cert.getAuthToken(), cert.getSessionId());
cert = buildCertificate(cert.getUsage(), user, cert.getAuthToken(), cert.getSessionId(),
cert.getSource());
PrivilegeContext privilegeContext = buildPrivilegeContext(cert, user);
this.privilegeContextMap.put(cert.getSessionId(), privilegeContext);
}
@ -1085,6 +1072,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
@Override
public void initiateChallengeFor(Usage usage, String username) {
initiateChallengeFor(usage, username, SOURCE_UNKNOWN);
}
@Override
public void initiateChallengeFor(Usage usage, String username, String source) {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
// get User
User user = this.persistenceHandler.getUser(username);
@ -1093,13 +1086,19 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
// initiate the challenge
this.userChallengeHandler.initiateChallengeFor(usage, user);
this.userChallengeHandler.initiateChallengeFor(usage, user, source);
logger.info(MessageFormat.format("Initiated Challenge for {0} with usage {1}", username, usage));
}
@Override
public Certificate validateChallenge(String username, String challenge) throws PrivilegeException {
return validateChallenge(username, challenge, "unknown");
}
@Override
public Certificate validateChallenge(String username, String challenge, String source) throws PrivilegeException {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
// get User
User user = this.persistenceHandler.getUser(username);
@ -1114,11 +1113,16 @@ 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);
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, userChallenge.getSource());
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
if (!source.equals("unknown") && !source.equals(userChallenge.getSource())) {
logger.warn("Challenge request and response source's are different: request: " + userChallenge.getSource()
+ " to " + source);
}
persistSessions();
logger.info(MessageFormat.format("Challenge validated for user {0} with usage {1}", username, usage));
@ -1127,6 +1131,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
@Override
public Certificate authenticate(String username, char[] password) {
return authenticate(username, password, "unknown");
}
@Override
public Certificate authenticate(String username, char[] password, String source) {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
try {
// username must be at least 2 characters in length
@ -1153,7 +1163,7 @@ 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);
Certificate certificate = buildCertificate(Usage.ANY, user, authToken, sessionId, source);
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
@ -1180,6 +1190,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
@Override
public Certificate authenticateSingleSignOn(Object data) throws PrivilegeException {
return authenticateSingleSignOn(data, "unknown");
}
@Override
public Certificate authenticateSingleSignOn(Object data, String source) throws PrivilegeException {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
if (this.ssoHandler == null)
throw new IllegalStateException("The SSO Handler is not configured!");
@ -1203,7 +1219,7 @@ 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);
Certificate certificate = buildCertificate(Usage.ANY, user, authToken, sessionId, source);
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
this.privilegeContextMap.put(sessionId, privilegeContext);
@ -1216,10 +1232,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
return certificate;
}
private Certificate buildCertificate(Usage usage, User user, String authToken, String sessionId) {
private Certificate buildCertificate(Usage usage, User user, String authToken, String sessionId, String source) {
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, new Date(), user.getLocale(), userRoles,
user.getUserState(), authToken, source, new Date(), user.getLocale(), userRoles,
new HashMap<>(user.getProperties()));
}
@ -1289,6 +1306,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String username = certificateStub.getUsername();
String sessionId = certificateStub.getSessionId();
String authToken = certificateStub.getAuthToken();
String source = certificateStub.getSource();
User user = this.persistenceHandler.getUser(username);
if (user == null) {
logger.error("Ignoring session data for missing user " + username);
@ -1307,7 +1325,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
// create a new certificate, with details of the user
Certificate certificate = buildCertificate(usage, user, authToken, sessionId);
Certificate certificate = buildCertificate(usage, user, authToken, sessionId, source);
certificate.setLastAccess(certificateStub.getLastAccess());
PrivilegeContext privilegeContext = buildPrivilegeContext(certificate, user);
@ -1514,6 +1532,13 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
@Override
public PrivilegeContext validate(Certificate certificate) throws PrivilegeException, NotAuthenticatedException {
return validate(certificate, "unknown");
}
@Override
public PrivilegeContext validate(Certificate certificate, String source)
throws PrivilegeException, NotAuthenticatedException {
DBC.PRE.assertNotEmpty("source must not be empty!", source);
// certificate must not be null
if (certificate == null)
@ -1545,6 +1570,12 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
certificate.setLastAccess(new Date());
// TODO decide if we want to assert source did not change!
if (!source.equals(SOURCE_UNKNOWN) && !certificate.getSource().equals(source)) {
logger.warn("Source has changed for certificate " + certificate.toString() + " to " + source);
}
return privilegeContext;
}
@ -1576,7 +1607,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
@Override
public boolean persistSessions(Certificate certificate) {
public boolean persistSessions(Certificate certificate, String source) {
// validate who is doing this
PrivilegeContext prvCtx = validate(certificate);
@ -1586,10 +1617,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
@Override
public boolean reload(Certificate certificate) {
public boolean reload(Certificate certificate, String source) {
// validate who is doing this
PrivilegeContext prvCtx = validate(certificate);
PrivilegeContext prvCtx = validate(certificate, source);
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_ACTION, PRIVILEGE_ACTION_RELOAD));
return this.persistenceHandler.reload();
@ -1723,24 +1754,25 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
private void handleSecretParams(Map<String, String> parameterMap) {
if (!this.persistSessions)
return;
String secretKeyS = parameterMap.get(PARAM_SECRET_KEY);
if (StringHelper.isEmpty(secretKeyS)) {
String msg = "Parameter {0} may not be empty if parameter {1} is enabled."; //$NON-NLS-1$
String msg = "Parameter {0} may not be empty"; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_SECRET_KEY, PARAM_PRIVILEGE_CONFLICT_RESOLUTION);
throw new PrivilegeModelException(msg);
}
String secretSaltS = parameterMap.get(PARAM_SECRET_SALT);
if (StringHelper.isEmpty(secretSaltS)) {
String msg = "Parameter {0} may not be empty if parameter {1} is enabled."; //$NON-NLS-1$
String msg = "Parameter {0} may not be empty"; //$NON-NLS-1$
msg = MessageFormat.format(msg, PARAM_SECRET_SALT, PARAM_PRIVILEGE_CONFLICT_RESOLUTION);
throw new PrivilegeModelException(msg);
}
this.secretKey = AesCryptoHelper.buildSecret(secretKeyS.toCharArray(), secretSaltS.getBytes());
// build our identifier
byte[] encrypt = AesCryptoHelper.encrypt(this.secretKey, "PrivilegeHandler".getBytes());
this.identifier = Base64.getEncoder().encodeToString(encrypt);
}
private void validatePrivilegeConflicts() {
@ -1956,7 +1988,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String sessionId = UUID.randomUUID().toString();
// create a new certificate, with details of the user
Certificate systemUserCertificate = buildCertificate(Usage.ANY, user, authToken, sessionId);
Certificate systemUserCertificate = buildCertificate(Usage.ANY, user, authToken, sessionId, this.identifier);
// create and save a new privilege context
PrivilegeContext privilegeContext = buildPrivilegeContext(systemUserCertificate, user);

View File

@ -553,6 +553,18 @@ public interface PrivilegeHandler {
*/
void initiateChallengeFor(Usage usage, String username);
/**
* Initiate a password reset challenge for the given username
*
* @param usage
* the usage for which the challenge is requested
* @param username
* the username of the user to initiate the challenge for
* @param source
* the source of the challenge
*/
void initiateChallengeFor(Usage usage, String username, String source);
/**
* Validate the response of a challenge for the given username
*
@ -569,6 +581,24 @@ public interface PrivilegeHandler {
*/
Certificate validateChallenge(String username, String challenge) throws PrivilegeException;
/**
* Validate the response of a challenge for the given username
*
* @param username
* the username of the user for which the challenge is to be validated
* @param challenge
* the challenge from the user
* @param source
* the source of the challenge validation
*
* @return certificate with which the user can access the system with the {@link Usage} set to the value from the
* initiated challenge
*
* @throws PrivilegeException
* if anything goes wrong
*/
Certificate validateChallenge(String username, String challenge, String source) throws PrivilegeException;
/**
* Authenticates a user by validating that a {@link User} for the given username and password exist and then returns
* a {@link Certificate} with which this user may then perform actions
@ -586,6 +616,25 @@ public interface PrivilegeHandler {
*/
Certificate authenticate(String username, char[] password) throws AccessDeniedException;
/**
* Authenticates a user by validating that a {@link User} for the given username and password exist and then returns
* a {@link Certificate} with which this user may then perform actions
*
* @param username
* the username of the {@link User} which is registered in the {@link PersistenceHandler}
* @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 source
* the source of the authentication request, i.e. remote IP
*
* @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) throws AccessDeniedException;
/**
* Authenticates a user on a remote Single Sign On service. This is implemented by the
*
@ -599,6 +648,21 @@ public interface PrivilegeHandler {
*/
Certificate authenticateSingleSignOn(Object data) throws PrivilegeException;
/**
* Authenticates a user on a remote Single Sign On service. This is implemented by the
*
* @param data
* the data to perform the SSO
* @param source
* the source of the SSO authentication
*
* @return the {@link Certificate} for the user
*
* @throws PrivilegeException
* if something goes wrong with the SSO
*/
Certificate authenticateSingleSignOn(Object data, String source) throws PrivilegeException;
/**
* Invalidates the session for the given {@link Certificate}, effectively logging out the user who was authenticated
* with the credentials associated to the given {@link Certificate}
@ -630,6 +694,28 @@ public interface PrivilegeHandler {
*/
PrivilegeContext validate(Certificate certificate) throws PrivilegeException;
/**
* Checks if the given {@link Certificate} is valid. This means that the certificate is for a valid session and that
* the user exists for the certificate. This method checks if the {@link Certificate} has been tampered with
*
* Returns the {@link PrivilegeContext} for the given {@link Certificate}. The {@link PrivilegeContext} is an
* encapsulated state of a user's privileges so that for the duration of a user's call, the user can perform their
* actions and do not need to access the {@link PrivilegeHandler} anymore
*
* @param certificate
* the {@link Certificate} to check
* @param source
* the source, e.g. remote IP for this validation request
*
* @return the {@link PrivilegeContext} for the given {@link Certificate}
*
* @throws PrivilegeException
* if there is anything wrong with this certificate
* @throws NotAuthenticatedException
* if the certificate has expired
*/
PrivilegeContext validate(Certificate certificate, String source) throws PrivilegeException;
/**
* Validate that the given password meets certain requirements. What these requirements are is a decision made by
* the concrete implementation
@ -651,13 +737,15 @@ public interface PrivilegeHandler {
*
* @param certificate
* the {@link Certificate} of the user which has the privilege to perform this action
* @param source
* the source of the request
*
* @return true if the reload was successful, false if something went wrong
*
* @throws AccessDeniedException
* if the users of the given certificate does not have the privilege to perform this action
*/
boolean reload(Certificate certificate);
boolean reload(Certificate certificate, String source);
/**
* Persists any changes to the privilege data model. Changes are thus not persisted immediately, but must be
@ -678,13 +766,15 @@ public interface PrivilegeHandler {
*
* @param certificate
* the {@link Certificate} of the user which has the privilege to perform this action
* @param source
* the source of the request
*
* @return true if changes were persisted, false if not (i.e. not enabled)
*
* @throws AccessDeniedException
* if the users of the given certificate does not have the privilege to perform this action
*/
boolean persistSessions(Certificate certificate) throws AccessDeniedException;
boolean persistSessions(Certificate certificate, String source) throws AccessDeniedException;
/**
* Special method to perform work as a System user, meaning the given systemUsername corresponds to an account which

View File

@ -34,8 +34,7 @@ public abstract class UserChallengeHandler {
* @return a new challenge
*/
protected String generateChallenge() {
String challenge = CodeGenerator.alphaNumericUpper(12);
return challenge;
return CodeGenerator.alphaNumericUpper(12);
}
/**
@ -45,11 +44,13 @@ public abstract class UserChallengeHandler {
* the {@link Usage} for this certificate
* @param user
* the user for which to initiate the challenge for
* @param source
* the source of the challenge initialization
*/
public synchronized void initiateChallengeFor(Usage usage, User user) {
public synchronized void initiateChallengeFor(Usage usage, User user, String source) {
String challenge = generateChallenge();
UserChallenge userChallenge = new UserChallenge(usage, user, challenge);
UserChallenge userChallenge = new UserChallenge(usage, user, challenge, source);
this.challenges.put(user, userChallenge);
sendChallengeToUser(user, challenge);
@ -94,7 +95,9 @@ public abstract class UserChallengeHandler {
* Sends the challenge to the user
*
* @param user
* the user
* @param challenge
* the challenge
*/
public abstract void sendChallengeToUser(User user, String challenge);
}

View File

@ -228,6 +228,11 @@ public class XmlConstants {
*/
public static final String XML_ATTR_AUTH_TOKEN = "authToken";
/**
* XML_ATTR_SOURCE = "source" :
*/
public static final String XML_ATTR_SOURCE = "source";
/**
* XML_ATTR_LOCALE = "locale" :
*/

View File

@ -26,7 +26,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, byte[])}
* PrivilegeHandler#authenticate(String, char[])}
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@ -39,6 +39,7 @@ public final class Certificate implements Serializable {
private final String lastname;
private final UserState userState;
private final String authToken;
private final String source;
private final Date loginTime;
private final Set<String> userRoles;
@ -76,7 +77,7 @@ public final class Certificate implements Serializable {
* 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, Date loginTime, Locale locale, Set<String> userRoles,
UserState userState, String authToken, String source, Date loginTime, Locale locale, Set<String> userRoles,
Map<String, String> propertyMap) {
// validate arguments are not null
@ -95,6 +96,9 @@ public final class Certificate implements Serializable {
if (usage == null) {
throw new PrivilegeException("usage is null!"); //$NON-NLS-1$
}
if (source == null) {
throw new PrivilegeException("source is null!"); //$NON-NLS-1$
}
this.usage = usage;
this.sessionId = sessionId;
@ -103,6 +107,7 @@ public final class Certificate implements Serializable {
this.lastname = lastname;
this.userState = userState;
this.authToken = authToken;
this.source = source;
this.loginTime = loginTime;
// if no locale is given, set default
@ -120,20 +125,10 @@ public final class Certificate implements Serializable {
this.lastAccess = new Date();
}
/**
* Returns the {@link Usage}
*
* @return the {@link Usage}
*/
public Usage getUsage() {
return this.usage;
}
/**
* Returns the set or roles this user has
*
* @return the user's roles
*/
public Set<String> getUserRoles() {
return this.userRoles;
}
@ -171,93 +166,54 @@ public final class Certificate implements Serializable {
return this.propertyMap.get(key);
}
/**
* @return the locale
*/
public Locale getLocale() {
return this.locale;
}
/**
* @param locale
* the locale to set
*/
public void setLocale(Locale locale) {
this.locale = locale;
}
/**
* @return the sessionId
*/
public String getSessionId() {
return this.sessionId;
}
/**
* @return the username
*/
public String getUsername() {
return this.username;
}
/**
* @return the firstname
*/
public String getFirstname() {
return this.firstname;
}
/**
* @return the lastname
*/
public String getLastname() {
return this.lastname;
}
/**
* @return the userState
*/
public UserState getUserState() {
return userState;
}
/**
* @return the loginTime
*/
public Date getLoginTime() {
return this.loginTime;
}
/**
* Returns the authToken if the given authPassword is correct, null otherwise
*
* @return the authToken if the given authPassword is correct, null otherwise
*/
public String getAuthToken() {
return this.authToken;
}
/**
* @return the lastAccess
*/
public String getSource() {
return this.source;
}
public Date getLastAccess() {
return this.lastAccess;
}
/**
* @param lastAccess
* the lastAccess to set
*/
public void setLastAccess(Date lastAccess) {
this.lastAccess = lastAccess;
}
/**
* Returns a string representation of this object displaying its concrete type and its values
*
* @see java.lang.Object#toString()
*/
@SuppressWarnings("nls")
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@ -276,6 +232,9 @@ public final class Certificate implements Serializable {
builder.append(this.lastname);
}
builder.append(", source=");
builder.append(this.source);
builder.append(", locale=");
builder.append(this.locale);

View File

@ -7,14 +7,16 @@ import li.strolch.privilege.model.Usage;
public class UserChallenge {
private final User user;
private final String challenge;
private final String source;
private final LocalDateTime initiated;
private boolean fulfilled;
private Usage usage;
public UserChallenge(Usage usage, User user, String challenge) {
public UserChallenge(Usage usage, User user, String challenge, String source) {
this.usage = usage;
this.user = user;
this.challenge = challenge;
this.source = source;
this.initiated = LocalDateTime.now();
}
@ -30,6 +32,10 @@ public class UserChallenge {
return this.challenge;
}
public String getSource() {
return this.source;
}
public LocalDateTime getInitiated() {
return this.initiated;
}

View File

@ -63,6 +63,9 @@ public class CertificateStubsDomWriter {
// authToken;
certElement.setAttribute(XmlConstants.XML_ATTR_AUTH_TOKEN, cert.getAuthToken());
// source;
certElement.setAttribute(XmlConstants.XML_ATTR_SOURCE, cert.getSource());
// locale;
certElement.setAttribute(XmlConstants.XML_ATTR_LOCALE, cert.getLocale().toString());

View File

@ -15,6 +15,9 @@
*/
package li.strolch.privilege.xml;
import static li.strolch.privilege.handler.DefaultPrivilegeHandler.SOURCE_UNKNOWN;
import static li.strolch.utils.helper.StringHelper.isEmpty;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
@ -62,6 +65,7 @@ public class CertificateStubsSaxReader extends DefaultHandler {
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 = new Locale(attributes.getValue(XmlConstants.XML_ATTR_LOCALE));
stub.loginTime = ISO8601FormatFactory.getInstance()
.parseDate(attributes.getValue(XmlConstants.XML_ATTR_LOGIN_TIME));
@ -72,6 +76,9 @@ public class CertificateStubsSaxReader extends DefaultHandler {
DBC.INTERIM.assertNotEmpty("username missing on sessions data!", stub.username);
DBC.INTERIM.assertNotEmpty("authToken missing on sessions data!", stub.authToken);
if (isEmpty(stub.source))
stub.source = SOURCE_UNKNOWN;
this.stubs.add(stub);
break;
@ -85,6 +92,7 @@ public class CertificateStubsSaxReader extends DefaultHandler {
private String sessionId;
private String username;
private String authToken;
private String source;
private Locale locale;
private Date loginTime;
private Date lastAccess;
@ -105,6 +113,10 @@ public class CertificateStubsSaxReader extends DefaultHandler {
return authToken;
}
public String getSource() {
return source;
}
public Locale getLocale() {
return locale;
}

View File

@ -145,6 +145,19 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
return certificate;
}
@Override
public Certificate authenticate(String username, char[] password, String source) {
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);
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 authenticateSingleSignOn(Object data) {
Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data);
@ -155,6 +168,16 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
return certificate;
}
@Override
public Certificate authenticateSingleSignOn(Object data, String source) {
Certificate certificate = this.privilegeHandler.authenticateSingleSignOn(data, source);
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 validate(String authToken) throws StrolchNotAuthenticatedException {
DBC.PRE.assertNotEmpty("authToken must be set!", authToken); //$NON-NLS-1$
@ -167,6 +190,18 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
return validate(certificate).getCertificate();
}
@Override
public Certificate validate(String authToken, String source) throws StrolchNotAuthenticatedException {
DBC.PRE.assertNotEmpty("authToken must be set!", authToken); //$NON-NLS-1$
Certificate certificate = this.certificateMap.get(authToken);
if (certificate == null)
throw new StrolchNotAuthenticatedException(
MessageFormat.format("No certificate exists for sessionId {0}", authToken)); //$NON-NLS-1$
return validate(certificate, source).getCertificate();
}
@Override
public PrivilegeContext validate(Certificate certificate) throws StrolchNotAuthenticatedException {
try {
@ -182,9 +217,24 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
}
}
@Override
public PrivilegeContext validate(Certificate certificate, String source) throws StrolchNotAuthenticatedException {
try {
PrivilegeContext privilegeContext = this.privilegeHandler.validate(certificate, source);
if (this.persistSessionsTask != null)
this.persistSessionsTask = getScheduledExecutor("SessionHandler")
.schedule(this::persistSessions, 5, TimeUnit.SECONDS);
return privilegeContext;
} catch (PrivilegeException e) {
throw new StrolchNotAuthenticatedException(e.getMessage(), e);
}
}
private void persistSessions() {
try {
runAsAgent(ctx -> this.privilegeHandler.getPrivilegeHandler().persistSessions(ctx.getCertificate()));
runAsAgent(ctx -> this.privilegeHandler.getPrivilegeHandler().persistSessions(ctx.getCertificate(), ctx.getCertificate().getSource()));
} catch (Exception e) {
logger.error("Failed to persist sessions", e);
} finally {
@ -209,6 +259,11 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
this.privilegeHandler.getPrivilegeHandler().initiateChallengeFor(usage, username);
}
@Override
public void initiateChallengeFor(Usage usage, String username, String source) {
this.privilegeHandler.getPrivilegeHandler().initiateChallengeFor(usage, username, source);
}
@Override
public Certificate validateChallenge(String username, String challenge) throws PrivilegeException {
DBC.PRE.assertNotEmpty("username must be set!", username); //$NON-NLS-1$
@ -222,6 +277,20 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
return certificate;
}
@Override
public Certificate validateChallenge(String username, String challenge, String source) throws PrivilegeException {
DBC.PRE.assertNotEmpty("username must be set!", username); //$NON-NLS-1$
DBC.PRE.assertNotEmpty("challenge must be set", challenge); //$NON-NLS-1$
Certificate certificate = this.privilegeHandler.getPrivilegeHandler()
.validateChallenge(username, challenge, source);
this.certificateMap.put(certificate.getAuthToken(), certificate);
logger.info(MessageFormat.format("{0} sessions currently active.", this.certificateMap.size())); //$NON-NLS-1$
return certificate;
}
private void checkSessionsForTimeout() {
Map<String, Certificate> certificateMap;
synchronized (this.certificateMap) {
@ -253,9 +322,8 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
}
@Override
public UserSession getSession(Certificate certificate, String sessionId)
throws AccessDeniedException, PrivilegeException {
PrivilegeContext ctx = this.privilegeHandler.validate(certificate);
public UserSession getSession(Certificate certificate, String source, String sessionId) throws PrivilegeException {
PrivilegeContext ctx = this.privilegeHandler.validate(certificate, source);
ctx.assertHasPrivilege(PRIVILEGE_GET_SESSION);
synchronized (this.certificateMap) {
for (Certificate cert : certificateMap.values()) {
@ -270,8 +338,8 @@ public class DefaultStrolchSessionHandler extends StrolchComponent implements St
}
@Override
public List<UserSession> getSessions(Certificate certificate) {
PrivilegeContext ctx = this.privilegeHandler.validate(certificate);
public List<UserSession> getSessions(Certificate certificate, String source) {
PrivilegeContext ctx = this.privilegeHandler.validate(certificate, source);
ctx.assertHasPrivilege(PRIVILEGE_GET_SESSION);
List<UserSession> sessions = new ArrayList<>(this.certificateMap.size());
synchronized (this.certificateMap) {

View File

@ -23,6 +23,7 @@ import javax.ws.rs.core.MediaType;
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 MSG = "msg";

View File

@ -1,12 +1,12 @@
/*
* Copyright 2013 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed 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.
@ -27,7 +27,8 @@ import li.strolch.privilege.model.Usage;
import li.strolch.rest.model.UserSession;
/**
* The {@link StrolchSessionHandler} implements session management. It authenticates, validates and invalidates session depending on the concrete implementation
* The {@link StrolchSessionHandler} implements session management. It authenticates, validates and invalidates session
* depending on the concrete implementation
*
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@ -45,6 +46,20 @@ public interface StrolchSessionHandler {
*/
Certificate authenticate(String username, char[] password);
/**
* Authenticates a user with the given credentials
*
* @param username
* the username
* @param password
* the password
* @param source
* the source of the request
*
* @return the {@link Certificate} for the logged in user
*/
Certificate authenticate(String username, char[] password, String source);
/**
* Performs a single-sign-on with the given data, if SSO is enabled
*
@ -55,6 +70,18 @@ public interface StrolchSessionHandler {
*/
Certificate authenticateSingleSignOn(Object data);
/**
* Performs a single-sign-on with the given data, if SSO is enabled
*
* @param data
* the data to pass to the SSO handler
* @param source
* the source of the request
*
* @return the {@link Certificate} for the logged in user
*/
Certificate authenticateSingleSignOn(Object data, String source);
/**
* Validates that a {@link Certificate} exists with the given auth token and is still valid
*
@ -68,6 +95,21 @@ public interface StrolchSessionHandler {
*/
Certificate validate(String authToken) throws StrolchNotAuthenticatedException;
/**
* Validates that a {@link Certificate} exists with the given auth token and is still valid
*
* @param authToken
* the auth token for the certificate
* @param source
* the source of the request
*
* @return the {@link Certificate} for the given auth token
*
* @throws StrolchNotAuthenticatedException
* if no logged in user exists with the given auth token
*/
Certificate validate(String authToken, String source) throws StrolchNotAuthenticatedException;
/**
* Validate that the given {@link Certificate} is still valid
*
@ -81,21 +123,40 @@ public interface StrolchSessionHandler {
*/
PrivilegeContext validate(Certificate certificate) throws StrolchNotAuthenticatedException;
/**
* Validate that the given {@link Certificate} is still valid
*
* @param certificate
* the certificate to validate
* @param source
* the source of the request
*
* @return the {@link PrivilegeContext} for the given certificate to perform authorization checks against
*
* @throws StrolchNotAuthenticatedException
* if no logged in user exists with the given auth token
*/
PrivilegeContext validate(Certificate certificate, String source) throws StrolchNotAuthenticatedException;
/**
* Returns all the {@link UserSession}
*
* @param certificate
* the certificate to validate if the requester may perform this action
* @param source
* the source of the request
*
* @return the list of {@link UserSession}
*/
List<UserSession> getSessions(Certificate certificate);
List<UserSession> getSessions(Certificate certificate, String source);
/**
* Return the {@link UserSession} with the given sessionId
*
* @param certificate
* the certificate to validate if the requester may perform this action
* @param source
* the source of the request
* @param sessionId
* the id of the {@link UserSession} to return
*
@ -106,7 +167,8 @@ public interface StrolchSessionHandler {
* @throws PrivilegeException
* if the {@link UserSession} does not exist, or another issues arises
*/
UserSession getSession(Certificate certificate, String sessionId) throws AccessDeniedException, PrivilegeException;
UserSession getSession(Certificate certificate, String source, String sessionId)
throws AccessDeniedException, PrivilegeException;
/**
* Invalidates the given certificate
@ -146,6 +208,18 @@ public interface StrolchSessionHandler {
*/
void initiateChallengeFor(Usage usage, String username);
/**
* Initiate a password reset challenge for the given username
*
* @param usage
* the usage for which the challenge is requested
* @param username
* the username of the user to initiate the challenge for
* @param source
* the source of the request
*/
void initiateChallengeFor(Usage usage, String username, String source);
/**
* Validate the response of a challenge for the given username
*
@ -161,4 +235,22 @@ public interface StrolchSessionHandler {
* if anything goes wrong
*/
Certificate validateChallenge(String username, String challenge) throws PrivilegeException;
/**
* Validate the response of a challenge for the given username
*
* @param username
* the username of the user for which the challenge is to be validated
* @param challenge
* the challenge from the user
* @param source
* the source of the request
*
* @return certificate with which the user can access the system with the {@link Usage} set to the value from the
* initiated challenge
*
* @throws PrivilegeException
* if anything goes wrong
*/
Certificate validateChallenge(String username, String challenge, String source) throws PrivilegeException;
}

View File

@ -15,6 +15,8 @@
*/
package li.strolch.rest.endpoint;
import static li.strolch.rest.filters.AuthenticationRequestFilter.getRemoteIp;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.*;
@ -90,9 +92,10 @@ public class AuthenticationService {
}
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
Certificate certificate = sessionHandler.authenticate(usernameE.getAsString(), password);
String source = getRemoteIp(request);
Certificate certificate = sessionHandler.authenticate(usernameE.getAsString(), password, source);
return getAuthenticationResponse(request, loginResult, certificate);
return getAuthenticationResponse(request, loginResult, certificate, source);
} catch (InvalidCredentialsException e) {
logger.error("Authentication failed due to: " + e.getMessage());
@ -126,9 +129,10 @@ public class AuthenticationService {
try {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
Certificate certificate = sessionHandler.authenticateSingleSignOn(request.getUserPrincipal());
String source = getRemoteIp(request);
Certificate certificate = sessionHandler.authenticateSingleSignOn(request.getUserPrincipal(), source);
return getAuthenticationResponse(request, loginResult, certificate);
return getAuthenticationResponse(request, loginResult, certificate, source);
} catch (InvalidCredentialsException e) {
logger.error("Authentication failed due to: " + e.getMessage());
@ -156,14 +160,15 @@ public class AuthenticationService {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{authToken}")
public Response invalidateSession(@PathParam("authToken") String authToken) {
public Response invalidateSession(@Context HttpServletRequest request, @PathParam("authToken") String authToken) {
JsonObject logoutResult = new JsonObject();
try {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
Certificate certificate = sessionHandler.validate(authToken);
String source = getRemoteIp(request);
Certificate certificate = sessionHandler.validate(authToken, source);
sessionHandler.invalidate(certificate);
logoutResult.addProperty("username", certificate.getUsername());
@ -190,12 +195,13 @@ public class AuthenticationService {
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Path("{authToken}")
public Response validateSession(@PathParam("authToken") String authToken) {
public Response validateSession(@Context HttpServletRequest request, @PathParam("authToken") String authToken) {
try {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
sessionHandler.validate(authToken);
String source = getRemoteIp(request);
sessionHandler.validate(authToken, source);
return Response.ok().build();
@ -218,7 +224,7 @@ public class AuthenticationService {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Path("challenge")
public Response initiateChallenge(String data) {
public Response initiateChallenge(@Context HttpServletRequest request, String data) {
try {
JsonObject jsonObject = new JsonParser().parse(data).getAsJsonObject();
@ -226,7 +232,8 @@ public class AuthenticationService {
String usage = jsonObject.get("usage").getAsString();
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
sessionHandler.initiateChallengeFor(Usage.byValue(usage), username);
String source = getRemoteIp(request);
sessionHandler.initiateChallengeFor(Usage.byValue(usage), username, source);
return ResponseUtil.toResponse();
@ -251,7 +258,8 @@ public class AuthenticationService {
String challenge = jsonObject.get("challenge").getAsString();
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
Certificate certificate = sessionHandler.validateChallenge(username, challenge);
String source = getRemoteIp(request);
Certificate certificate = sessionHandler.validateChallenge(username, challenge, source);
jsonObject = new JsonObject();
jsonObject.addProperty("authToken", certificate.getAuthToken());
@ -278,10 +286,10 @@ public class AuthenticationService {
}
private Response getAuthenticationResponse(HttpServletRequest request, JsonObject loginResult,
Certificate certificate) {
Certificate certificate, String source) {
PrivilegeHandler privilegeHandler = RestfulStrolchComponent.getInstance().getContainer().getPrivilegeHandler();
PrivilegeContext privilegeContext = privilegeHandler.validate(certificate);
PrivilegeContext privilegeContext = privilegeHandler.validate(certificate, source);
loginResult.addProperty("sessionId", certificate.getSessionId());
loginResult.addProperty("authToken", certificate.getAuthToken());
loginResult.addProperty("username", certificate.getUsername());

View File

@ -53,8 +53,9 @@ public class StrolchJobsResource {
public Response getAll(@Context HttpServletRequest request, @Context HttpHeaders headers) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
String source = (String) request.getAttribute(StrolchRestfulConstants.STROLCH_REQUEST_SOURCE);
ComponentContainer container = RestfulStrolchComponent.getInstance().getContainer();
PrivilegeContext ctx = container.getPrivilegeHandler().validate(cert);
PrivilegeContext ctx = container.getPrivilegeHandler().validate(cert, source);
// assert user can access StrolchJobs
if (!ctx.hasRole(ROLE_STROLCH_ADMIN))
@ -62,7 +63,7 @@ public class StrolchJobsResource {
StrolchJobsHandler strolchJobsHandler = container.getComponent(StrolchJobsHandler.class);
List<StrolchJob> jobs = strolchJobsHandler.getJobs(cert).stream() //
List<StrolchJob> jobs = strolchJobsHandler.getJobs(cert, source).stream() //
.filter(job -> {
if (ctx.hasRole(ROLE_STROLCH_ADMIN))
return true;
@ -84,14 +85,15 @@ public class StrolchJobsResource {
try {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
String source = (String) request.getAttribute(StrolchRestfulConstants.STROLCH_REQUEST_SOURCE);
ComponentContainer container = RestfulStrolchComponent.getInstance().getContainer();
StrolchJobsHandler strolchJobsHandler = container.getComponent(StrolchJobsHandler.class);
StrolchJob job = strolchJobsHandler.getJob(cert, name);
StrolchJob job = strolchJobsHandler.getJob(cert, source, name);
// assert user can access StrolchJobs
PrivilegeContext ctx = container.getPrivilegeHandler().validate(cert);
PrivilegeContext ctx = container.getPrivilegeHandler().validate(cert, source);
if (!ctx.hasRole(ROLE_STROLCH_ADMIN))
ctx.validateAction(job);

View File

@ -52,11 +52,12 @@ public class UserSessionsService {
@Produces(MediaType.APPLICATION_JSON)
public Response querySessions(@Context HttpServletRequest request, @BeanParam QueryData queryData) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
String source = (String) request.getAttribute(StrolchRestfulConstants.STROLCH_REQUEST_SOURCE);
logger.info("[" + cert.getUsername() + "] Querying user sessions...");
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
String query = queryData.getQuery();
List<UserSession> sessions = sessionHandler.getSessions(cert);
List<UserSession> sessions = sessionHandler.getSessions(cert, source);
SearchResult<UserSession> result = buildSimpleValueSearch(new ValueSearch<UserSession>(), query,
Arrays.asList( //
@ -75,9 +76,10 @@ public class UserSessionsService {
@Path("{sessionId}")
public Response getSession(@Context HttpServletRequest request, @PathParam("sessionId") String sessionId) {
Certificate cert = (Certificate) request.getAttribute(StrolchRestfulConstants.STROLCH_CERTIFICATE);
String source = (String) request.getAttribute(StrolchRestfulConstants.STROLCH_REQUEST_SOURCE);
logger.info("[" + cert.getUsername() + "] Returning session " + sessionId);
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance().getSessionHandler();
UserSession session = sessionHandler.getSession(cert, sessionId);
UserSession session = sessionHandler.getSession(cert, source, sessionId);
return Response.ok(session.toJson().toString(), MediaType.APPLICATION_JSON).build();
}

View File

@ -16,15 +16,15 @@
package li.strolch.rest.filters;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_CERTIFICATE;
import static li.strolch.rest.StrolchRestfulConstants.STROLCH_REQUEST_SOURCE;
import static li.strolch.utils.helper.StringHelper.isNotEmpty;
import javax.annotation.Priority;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.*;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.util.HashSet;
@ -48,7 +48,7 @@ import org.slf4j.LoggerFactory;
*
* <br>
*
* Sub classes should override {@link #validateSession(ContainerRequestContext)} to add further validation.
* Sub classes should override {@link #validateSession(ContainerRequestContext, String)} to add further validation.
*
* @author Reto Breitenmoser <reto.breitenmoser@4trees.ch>
* @author Robert von Burg <eitch@eitchnet.ch>
@ -59,6 +59,9 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthenticationRequestFilter.class);
@Context
private HttpServletRequest request;
private Set<String> unsecuredPaths;
/**
@ -101,12 +104,16 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String remoteIp = getRemoteIp(this.request);
logger.info("Remote IP: " + remoteIp + ": " + requestContext.getMethod() + " " + requestContext.getUriInfo()
.getRequestUri());
if (isUnsecuredPath(requestContext))
return;
try {
validateSession(requestContext);
validateSession(requestContext, remoteIp);
} catch (StrolchNotAuthenticatedException e) {
logger.error(e.getMessage());
@ -137,11 +144,13 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
*
* @param requestContext
* the request context for the secured path
* @param remoteIp
* the remote IP
*
* @return the certificate for the validated session, or null, of the request is aborted to no missing or invalid
* authorization token
*/
protected Certificate validateSession(ContainerRequestContext requestContext) {
protected Certificate validateSession(ContainerRequestContext requestContext, String remoteIp) {
String sessionId = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
if (StringHelper.isEmpty(sessionId)) {
@ -171,10 +180,34 @@ public class AuthenticationRequestFilter implements ContainerRequestFilter {
StrolchSessionHandler sessionHandler = RestfulStrolchComponent.getInstance()
.getComponent(StrolchSessionHandler.class);
Certificate certificate = sessionHandler.validate(sessionId);
Certificate certificate = sessionHandler.validate(sessionId, remoteIp);
requestContext.setProperty(STROLCH_CERTIFICATE, certificate);
requestContext.setProperty(STROLCH_REQUEST_SOURCE, remoteIp);
return certificate;
}
public static String getRemoteIp(HttpServletRequest request) {
String remoteUser = request.getRemoteUser();
String remoteHost = request.getRemoteHost();
String remoteAddr = request.getRemoteAddr();
StringBuilder sb = new StringBuilder();
if (isNotEmpty(remoteUser))
sb.append(remoteUser).append(": ");
if (remoteHost.equals(remoteAddr))
sb.append(remoteAddr);
else {
sb.append(remoteUser).append(": (").append(remoteAddr).append(")");
}
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (isNotEmpty(xForwardedFor))
sb.append(" (fwd)=> ").append(xForwardedFor);
return sb.toString();
}
}

View File

@ -1,12 +1,12 @@
/*
* Copyright 2015 Robert von Burg <eitch@eitchnet.ch>
*
*
* Licensed 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.
@ -31,6 +31,7 @@ public class UserSession {
private String username;
private String firstname;
private String lastname;
private String source;
private Set<String> userRoles;
private Locale locale;
private Date lastAccess;
@ -41,6 +42,7 @@ public class UserSession {
this.username = certificate.getUsername();
this.firstname = certificate.getFirstname();
this.lastname = certificate.getLastname();
this.source = certificate.getSource();
this.userRoles = certificate.getUserRoles();
this.locale = certificate.getLocale();
this.lastAccess = certificate.getLastAccess();
@ -74,6 +76,10 @@ public class UserSession {
return lastname;
}
public String getSource() {
return this.source;
}
public Set<String> getUserRoles() {
return userRoles;
}
@ -86,6 +92,7 @@ public class UserSession {
jsonObject.addProperty("username", this.username);
jsonObject.addProperty("firstname", this.firstname);
jsonObject.addProperty("lastname", this.lastname);
jsonObject.addProperty("source", this.source);
jsonObject.addProperty("locale", this.locale.toString());
jsonObject.addProperty("lastAccess", ISO8601FormatFactory.getInstance().formatDate(this.lastAccess));

View File

@ -56,14 +56,14 @@ 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, new Date(), null, new HashSet<>(), null),
testService);
new Certificate(null, null, null, null, null, null, null, null, new Date(), 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",
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);
ServiceResult svcResult = getServiceHandler().doService(badCert, testService);