Skip to content
Snippets Groups Projects
Unverified Commit 91469eba authored by Daniel's avatar Daniel
Browse files

MAINT, DOC: Refactoring "attempt" -> "replay", plus documentation.

parent 4abe1608
No related branches found
No related tags found
No related merge requests found
Showing
with 160 additions and 86 deletions
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
output: output:
file: "authtoken/admin_token_3_attempts.json" file: "authtoken/admin_token_3_attempts.json"
schedule: "0/10 * * ? * * *" schedule: "0/10 * * ? * * *"
maxAttempts: 3 maxReplays: 3
- roles: - roles:
- administration - administration
output: output:
......
...@@ -123,11 +123,12 @@ ONE_TIME_TOKEN_EXPIRES_MS=604800000 ...@@ -123,11 +123,12 @@ ONE_TIME_TOKEN_EXPIRES_MS=604800000
# Path to config file for one time tokens, for example authtoken.yml. # Path to config file for one time tokens, for example authtoken.yml.
AUTHTOKEN_CONFIG= AUTHTOKEN_CONFIG=
# Timeout after which a consumed one-time token expires regardless of the # Timeout after which a one-time token expires once it has been first consumed,
# maximum of attempts that are allowed for that token. This is only a default # regardless of the maximum of replays that are allowed for that token. This is
# value. The actual timeout of tokens can be configured otherwise. # only a default value. The actual timeout of tokens can be configured
# otherwise.
# 30 s # 30 s
ONE_TIME_TOKEN_ATTEMPTS_TIMEOUT_MS=30000 ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS=30000
# The value for the HTTP cache directive "max-age" # The value for the HTTP cache directive "max-age"
WEBUI_HTTP_HEADER_CACHE_MAX_AGE=28800 WEBUI_HTTP_HEADER_CACHE_MAX_AGE=28800
......
...@@ -123,6 +123,9 @@ public class CaosDBServer extends Application { ...@@ -123,6 +123,9 @@ public class CaosDBServer extends Application {
private static ArrayList<Runnable> preShutdownHooks = new ArrayList<Runnable>(); private static ArrayList<Runnable> preShutdownHooks = new ArrayList<Runnable>();
private static boolean START_BACKEND = true; private static boolean START_BACKEND = true;
private static boolean INSECURE = false; private static boolean INSECURE = false;
public static final String REQUEST_TIME_LOGGER = "REQUEST_TIME_LOGGER";
public static final String REQUEST_ERRORS_LOGGER = "REQUEST_ERRORS_LOGGER";
private static Scheduler SCHEDULER;
public static String getServerProperty(final String key) { public static String getServerProperty(final String key) {
return getServerProperties().getProperty(key); return getServerProperties().getProperty(key);
...@@ -527,10 +530,6 @@ public class CaosDBServer extends Application { ...@@ -527,10 +530,6 @@ public class CaosDBServer extends Application {
} }
} }
public static final String REQUEST_TIME_LOGGER = "REQUEST_TIME_LOGGER";
public static final String REQUEST_ERRORS_LOGGER = "REQUEST_ERRORS_LOGGER";
private static Scheduler SCHEDULER;
/** /**
* Specify the dispatching restlet that maps URIs to their associated resources for processing. * Specify the dispatching restlet that maps URIs to their associated resources for processing.
* *
...@@ -580,6 +579,7 @@ public class CaosDBServer extends Application { ...@@ -580,6 +579,7 @@ public class CaosDBServer extends Application {
private void setSessionCookies(final Response response) { private void setSessionCookies(final Response response) {
final Subject subject = SecurityUtils.getSubject(); final Subject subject = SecurityUtils.getSubject();
// if authenticated as a normal user: generate and set session cookie.
if (subject.isAuthenticated() if (subject.isAuthenticated()
&& subject.getPrincipal() != AnonymousAuthenticationToken.PRINCIPAL) { && subject.getPrincipal() != AnonymousAuthenticationToken.PRINCIPAL) {
final SessionToken sessionToken = SessionToken.generate(subject); final SessionToken sessionToken = SessionToken.generate(subject);
......
...@@ -89,8 +89,8 @@ public class ServerProperties extends Properties { ...@@ -89,8 +89,8 @@ public class ServerProperties extends Properties {
public static final String KEY_SESSION_TIMEOUT_MS = "SESSION_TIMEOUT_MS"; public static final String KEY_SESSION_TIMEOUT_MS = "SESSION_TIMEOUT_MS";
public static final String KEY_ONE_TIME_TOKEN_EXPIRES_MS = "ONE_TIME_TOKEN_EXPIRES_MS"; public static final String KEY_ONE_TIME_TOKEN_EXPIRES_MS = "ONE_TIME_TOKEN_EXPIRES_MS";
public static final String KEY_ONE_TIME_TOKEN_ATTEMPTS_TIMEOUT_MS = public static final String KEY_ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS =
"ONE_TIME_TOKEN_ATTEMPTS_TIMEOUT_MS"; "ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS";
public static final String KEY_CACHE_CONF_LOC = "CACHE_CONF_LOC"; public static final String KEY_CACHE_CONF_LOC = "CACHE_CONF_LOC";
public static final String KEY_CACHE_DISABLE = "CACHE_DISABLE"; public static final String KEY_CACHE_DISABLE = "CACHE_DISABLE";
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
* *
* Copyright (C) 2018 Research Group Biomedical Physics, * Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
* Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
* Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
...@@ -124,9 +126,7 @@ public class AuthenticationUtils { ...@@ -124,9 +126,7 @@ public class AuthenticationUtils {
if (token != null && token.isValid()) { if (token != null && token.isValid()) {
t = new Timestamp(token.getExpires()).toString().replaceFirst(" ", "T"); t = new Timestamp(token.getExpires()).toString().replaceFirst(" ", "T");
exp_in_sec = (int) Math.ceil(token.getTimeout() / 1000.0); // new exp_in_sec = (int) Math.ceil(token.getTimeout() / 1000.0); // new expiration time
// expiration
// time.
return new CookieSetting( return new CookieSetting(
0, 0,
AuthenticationUtils.SESSION_TIMEOUT_COOKIE, AuthenticationUtils.SESSION_TIMEOUT_COOKIE,
......
...@@ -49,6 +49,7 @@ public class CaosDBAuthorizingRealm extends AuthorizingRealm { ...@@ -49,6 +49,7 @@ public class CaosDBAuthorizingRealm extends AuthorizingRealm {
final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo(); final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
Object principal = principals.getPrimaryPrincipal(); Object principal = principals.getPrimaryPrincipal();
// Add explicitly given roles and permissions.
if (principal instanceof SelfValidatingAuthenticationToken) { if (principal instanceof SelfValidatingAuthenticationToken) {
Collection<String> sessionPermissions = Collection<String> sessionPermissions =
getSessionPermissions((SelfValidatingAuthenticationToken) principal); getSessionPermissions((SelfValidatingAuthenticationToken) principal);
...@@ -60,8 +61,7 @@ public class CaosDBAuthorizingRealm extends AuthorizingRealm { ...@@ -60,8 +61,7 @@ public class CaosDBAuthorizingRealm extends AuthorizingRealm {
authzInfo.addStringPermissions(sessionPermissions); authzInfo.addStringPermissions(sessionPermissions);
} }
// find all roles which are associated with this principal in this // Find all roles which are associated with this principal in this realm.
// realm.
final Set<String> principalRoles = final Set<String> principalRoles =
UserSources.resolve((Principal) principals.getPrimaryPrincipal()); UserSources.resolve((Principal) principals.getPrimaryPrincipal());
if (principalRoles != null) { if (principalRoles != null) {
......
...@@ -33,6 +33,7 @@ import org.apache.shiro.authc.AuthenticationException; ...@@ -33,6 +33,7 @@ import org.apache.shiro.authc.AuthenticationException;
public class CaosDBRolePermissionResolver { public class CaosDBRolePermissionResolver {
/** Return CaosPermission with the rules which are associated with the roles. */
public CaosPermission resolvePermissionsInRole(final Set<String> roles) { public CaosPermission resolvePermissionsInRole(final Set<String> roles) {
final HashSet<PermissionRule> rules = new HashSet<PermissionRule>(); final HashSet<PermissionRule> rules = new HashSet<PermissionRule>();
for (final String role : roles) { for (final String role : roles) {
......
...@@ -5,9 +5,9 @@ public class Config { ...@@ -5,9 +5,9 @@ public class Config {
private String[] roles = {}; private String[] roles = {};
private String purpose = null; private String purpose = null;
private OneTimeTokenToFile output = null; private OneTimeTokenToFile output = null;
private int maxAttempts = 1; private int maxReplays = 1;
private int timeout = OneTimeAuthenticationToken.DEFAULT_TIMEOUT_MS; private int timeout = OneTimeAuthenticationToken.DEFAULT_TIMEOUT_MS;
private int attemptsTimeout = OneTimeAuthenticationToken.DEFAULT_ATTEMPTS_TIMEOUT_MS; private int replaysTimeout = OneTimeAuthenticationToken.DEFAULT_REPLAYS_TIMEOUT_MS;
private String name = AnonymousAuthenticationToken.PRINCIPAL.getUsername(); private String name = AnonymousAuthenticationToken.PRINCIPAL.getUsername();
public Config() {} public Config() {}
...@@ -28,20 +28,20 @@ public class Config { ...@@ -28,20 +28,20 @@ public class Config {
this.timeout = timeout; this.timeout = timeout;
} }
public void setAttemptsTimeoutSeconds(int seconds) { public void setReplaysTimeoutSeconds(int seconds) {
this.setAttemptsTimeout(seconds * 1000); this.setReplaysTimeout(seconds * 1000);
} }
public void setExpiresAfterSeconds(int seconds) { public void setExpiresAfterSeconds(int seconds) {
this.setTimeout(seconds * 1000); this.setTimeout(seconds * 1000);
} }
public void setMaxAttempts(int maxAttempts) { public void setMaxReplays(int maxReplays) {
this.maxAttempts = maxAttempts; this.maxReplays = maxReplays;
} }
public int getMaxAttempts() { public int getMaxReplays() {
return maxAttempts; return maxReplays;
} }
public String[] getPermissions() { public String[] getPermissions() {
...@@ -76,11 +76,11 @@ public class Config { ...@@ -76,11 +76,11 @@ public class Config {
this.output = output; this.output = output;
} }
public int getAttemptsTimeout() { public int getReplaysTimeout() {
return attemptsTimeout; return replaysTimeout;
} }
public void setAttemptsTimeout(int attemptsTimeout) { public void setReplaysTimeout(int replaysTimeout) {
this.attemptsTimeout = attemptsTimeout; this.replaysTimeout = replaysTimeout;
} }
} }
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
* *
* Copyright (C) 2018 Research Group Biomedical Physics, * Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
* Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
* Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
...@@ -43,18 +45,18 @@ import org.slf4j.LoggerFactory; ...@@ -43,18 +45,18 @@ import org.slf4j.LoggerFactory;
public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToken { public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToken {
public static final long DEFAULT_MAX_ATTEMPTS = 1L; public static final long DEFAULT_MAX_REPLAYS = 1L;
public static final int DEFAULT_ATTEMPTS_TIMEOUT_MS = public static final int DEFAULT_REPLAYS_TIMEOUT_MS =
Integer.parseInt( Integer.parseInt(
CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_ATTEMPTS_TIMEOUT_MS)); CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS));
public static final int DEFAULT_TIMEOUT_MS = public static final int DEFAULT_TIMEOUT_MS =
Integer.parseInt( Integer.parseInt(
CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS)); CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS));
public static final String REALM_NAME = "OneTimeAuthenticationToken"; // TODO move to UserSources public static final String REALM_NAME = "OneTimeAuthenticationToken"; // TODO move to UserSources
public static final Logger LOGGER = LoggerFactory.getLogger(OneTimeAuthenticationToken.class); public static final Logger LOGGER = LoggerFactory.getLogger(OneTimeAuthenticationToken.class);
private long maxAttempts; private long maxReplays;
private long attemptsTimeout; private long replaysTimeout;
public OneTimeAuthenticationToken( public OneTimeAuthenticationToken(
final Principal principal, final Principal principal,
...@@ -64,16 +66,16 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke ...@@ -64,16 +66,16 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke
final String checksum, final String checksum,
final String[] permissions, final String[] permissions,
final String[] roles, final String[] roles,
final long maxAttempts, final long maxReplays,
final long attemptsTimeout) { final long replaysTimeout) {
super(principal, date, timeout, salt, checksum, permissions, roles); super(principal, date, timeout, salt, checksum, permissions, roles);
this.attemptsTimeout = attemptsTimeout; this.replaysTimeout = replaysTimeout;
this.maxAttempts = maxAttempts; this.maxReplays = maxReplays;
consume(); consume();
} }
public long getAttemptsTimeout() { public long getReplaysTimeout() {
return attemptsTimeout; return replaysTimeout;
} }
public void consume() { public void consume() {
...@@ -85,15 +87,15 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke ...@@ -85,15 +87,15 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke
final long timeout, final long timeout,
final String[] permissions, final String[] permissions,
final String[] roles, final String[] roles,
final Long maxAttempts, final Long maxReplays,
final Long attemptsTimeout) { final Long replaysTimeout) {
super( super(
principal, principal,
timeout, timeout,
permissions, permissions,
roles, roles,
defaultIfNull(maxAttempts, DEFAULT_MAX_ATTEMPTS), defaultIfNull(maxReplays, DEFAULT_MAX_REPLAYS),
defaultIfNull(attemptsTimeout, DEFAULT_ATTEMPTS_TIMEOUT_MS)); defaultIfNull(replaysTimeout, DEFAULT_REPLAYS_TIMEOUT_MS));
} }
private static final long serialVersionUID = -1072740888045267613L; private static final long serialVersionUID = -1072740888045267613L;
...@@ -113,10 +115,10 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke ...@@ -113,10 +115,10 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke
final long timeout = (Long) array[6]; final long timeout = (Long) array[6];
final String salt = (String) array[7]; final String salt = (String) array[7];
final String checksum = (String) array[8]; final String checksum = (String) array[8];
final long maxAttempts = (Long) array[9]; final long maxReplays = (Long) array[9];
final long attemptsTimeout = (Long) array[10]; final long replaysTimeout = (Long) array[10];
return new OneTimeAuthenticationToken( return new OneTimeAuthenticationToken(
principal, date, timeout, salt, checksum, permissions, roles, maxAttempts, attemptsTimeout); principal, date, timeout, salt, checksum, permissions, roles, maxReplays, replaysTimeout);
} }
private static OneTimeAuthenticationToken generate( private static OneTimeAuthenticationToken generate(
...@@ -124,11 +126,11 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke ...@@ -124,11 +126,11 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke
final String[] permissions, final String[] permissions,
final String[] roles, final String[] roles,
final long timeout, final long timeout,
final long maxAttempts, final long maxReplays,
final long attemptsTimeout) { final long replaysTimeout) {
return new OneTimeAuthenticationToken( return new OneTimeAuthenticationToken(
principal, timeout, permissions, roles, maxAttempts, attemptsTimeout); principal, timeout, permissions, roles, maxReplays, replaysTimeout);
} }
public static List<Config> loadConfig(InputStream input) throws Exception { public static List<Config> loadConfig(InputStream input) throws Exception {
...@@ -174,8 +176,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke ...@@ -174,8 +176,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke
c.getPermissions(), c.getPermissions(),
c.getRoles(), c.getRoles(),
c.getTimeout(), c.getTimeout(),
c.getMaxAttempts(), c.getMaxReplays(),
c.getAttemptsTimeout()); c.getReplaysTimeout());
} }
static Map<String, Config> purposes = new HashMap<>(); static Map<String, Config> purposes = new HashMap<>();
...@@ -212,8 +214,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke ...@@ -212,8 +214,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke
@Override @Override
protected void setFields(Object[] fields) { protected void setFields(Object[] fields) {
if (fields.length == 2) { if (fields.length == 2) {
this.maxAttempts = (long) fields[0]; this.maxReplays = (long) fields[0];
this.attemptsTimeout = (long) fields[1]; this.replaysTimeout = (long) fields[1];
} else { } else {
throw new InstantiationError("Too few fields."); throw new InstantiationError("Too few fields.");
} }
...@@ -230,8 +232,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke ...@@ -230,8 +232,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke
this.salt, this.salt,
calcChecksum((Object[]) this.permissions), calcChecksum((Object[]) this.permissions),
calcChecksum((Object[]) this.roles), calcChecksum((Object[]) this.roles),
this.maxAttempts, this.maxReplays,
this.attemptsTimeout, this.replaysTimeout,
pepper); pepper);
} }
...@@ -248,13 +250,13 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke ...@@ -248,13 +250,13 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke
this.timeout, this.timeout,
this.salt, this.salt,
this.checksum, this.checksum,
this.maxAttempts, this.maxReplays,
this.attemptsTimeout this.replaysTimeout
}); });
} }
public long getMaxAttempts() { public long getMaxReplays() {
return maxAttempts; return maxReplays;
} }
public static void resetConfig() { public static void resetConfig() {
......
...@@ -7,6 +7,10 @@ import java.util.List; ...@@ -7,6 +7,10 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationException;
/**
* Utility class to manage OTTs: mark as consumed, removed expired OTTs, manage maximum number of
* replays and replay timeout of tokens.
*/
class OneTimeTokenConsumedInfo { class OneTimeTokenConsumedInfo {
private static Map<String, OneTimeTokenConsumedInfo> consumedOneTimeTokens = new HashMap<>(); private static Map<String, OneTimeTokenConsumedInfo> consumedOneTimeTokens = new HashMap<>();
...@@ -24,6 +28,7 @@ class OneTimeTokenConsumedInfo { ...@@ -24,6 +28,7 @@ class OneTimeTokenConsumedInfo {
} }
} }
/** If the token is valid, consume it once and store this information. */
public static void consume(OneTimeAuthenticationToken oneTimeAuthenticationToken) { public static void consume(OneTimeAuthenticationToken oneTimeAuthenticationToken) {
if (oneTimeAuthenticationToken.isValid()) { if (oneTimeAuthenticationToken.isValid()) {
String key = OneTimeTokenConsumedInfo.getKey(oneTimeAuthenticationToken); String key = OneTimeTokenConsumedInfo.getKey(oneTimeAuthenticationToken);
...@@ -40,7 +45,7 @@ class OneTimeTokenConsumedInfo { ...@@ -40,7 +45,7 @@ class OneTimeTokenConsumedInfo {
} }
private OneTimeAuthenticationToken oneTimeAuthenticationToken; private OneTimeAuthenticationToken oneTimeAuthenticationToken;
private List<Long> attempts = new LinkedList<>(); private List<Long> replays = new LinkedList<>();
public OneTimeTokenConsumedInfo(OneTimeAuthenticationToken oneTimeAuthenticationToken) { public OneTimeTokenConsumedInfo(OneTimeAuthenticationToken oneTimeAuthenticationToken) {
this.oneTimeAuthenticationToken = oneTimeAuthenticationToken; this.oneTimeAuthenticationToken = oneTimeAuthenticationToken;
...@@ -50,30 +55,31 @@ class OneTimeTokenConsumedInfo { ...@@ -50,30 +55,31 @@ class OneTimeTokenConsumedInfo {
return token.checksum; return token.checksum;
} }
private int getNoOfAttempts() { private int getNoOfReplays() {
return attempts.size(); return replays.size();
} }
private long getMaxAttempts() { private long getMaxReplays() {
return oneTimeAuthenticationToken.getMaxAttempts(); return oneTimeAuthenticationToken.getMaxReplays();
} }
private long getAttemptTimeout() { private long getReplayTimeout() {
if (attempts.size() == 0) { if (replays.size() == 0) {
return Long.MAX_VALUE; return Long.MAX_VALUE;
} }
long firstAttemptTime = attempts.get(0); long firstReplayTime = replays.get(0);
return firstAttemptTime + oneTimeAuthenticationToken.getAttemptsTimeout(); return firstReplayTime + oneTimeAuthenticationToken.getReplaysTimeout();
} }
/** If there are still replays and time left, increase the replay counter by one. */
public void consume() { public void consume() {
synchronized (attempts) { synchronized (replays) {
if (getNoOfAttempts() >= getMaxAttempts()) { if (getNoOfReplays() >= getMaxReplays()) {
throw new AuthenticationException("One-time token was consumed too often."); throw new AuthenticationException("One-time token was consumed too often.");
} else if (getAttemptTimeout() < System.currentTimeMillis()) { } else if (getReplayTimeout() < System.currentTimeMillis()) {
throw new AuthenticationException("One-time token attempts timeout expired."); throw new AuthenticationException("One-time token replays timeout expired.");
} }
attempts.add(System.currentTimeMillis()); replays.add(System.currentTimeMillis());
} }
} }
......
...@@ -49,10 +49,11 @@ public class OneTimeTokenToFile implements Job { ...@@ -49,10 +49,11 @@ public class OneTimeTokenToFile implements Job {
this.schedule = schedule; this.schedule = schedule;
} }
/** If no schedule was set, immediately write the config to file, else schedule the job. */
public void init(Config config) throws IOException, SchedulerException { public void init(Config config) throws IOException, SchedulerException {
if (this.schedule != null) { if (this.schedule != null) {
OneTimeAuthenticationToken.generate(config); // test config OneTimeAuthenticationToken.generate(config); // test config, throw away token
JobDataMap map = new JobDataMap(); JobDataMap map = new JobDataMap();
map.put("config", config); map.put("config", config);
map.put("file", file); map.put("file", file);
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
* *
* Copyright (C) 2018 Research Group Biomedical Physics, * Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
* Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
* Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
...@@ -28,12 +30,28 @@ import java.util.Collection; ...@@ -28,12 +30,28 @@ import java.util.Collection;
import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.AuthenticationToken;
import org.eclipse.jetty.util.ajax.JSON; import org.eclipse.jetty.util.ajax.JSON;
/**
* These AuthenticationTokens are characterized by the following properties:
*
* <ul>
* <li>date: The creation time.
* <li>timeout: How long this token is valud after creation.
* <li>checksum: ? Used for validation, but how?
* <li>salt: Salt for the password checksum, may be used by inheriting classes.
* <li>pepper: A static property, generated when class is loaded and used until the server
* reboots. Hence all tokens of this kkinf invalidate when the server reboots.
*
* @todo Explain: Checksum
* @todo Is this really a pepper in the sense in which it is usually used?
*/
public abstract class SelfValidatingAuthenticationToken extends Principal public abstract class SelfValidatingAuthenticationToken extends Principal
implements AuthenticationToken { implements AuthenticationToken {
protected static final transient String PEPPER = Utils.getSecureFilename(32); protected static final transient String PEPPER = Utils.getSecureFilename(32);
private static final long serialVersionUID = -7212039848895531161L; private static final long serialVersionUID = -7212039848895531161L;
// date is the token creation time, in ms since 1970
protected final long date; protected final long date;
// token validity duration
protected final long timeout; protected final long timeout;
protected final String checksum; protected final String checksum;
protected final String salt; protected final String salt;
...@@ -93,6 +111,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal ...@@ -93,6 +111,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal
this.checksum = checksum == null && newChecksum ? calcChecksum() : checksum; this.checksum = checksum == null && newChecksum ? calcChecksum() : checksum;
} }
/** Customizable customization method, will be called with the remaining constructor arguments. */
protected abstract void setFields(Object[] fields); protected abstract void setFields(Object[] fields);
public SelfValidatingAuthenticationToken( public SelfValidatingAuthenticationToken(
...@@ -120,8 +139,10 @@ public abstract class SelfValidatingAuthenticationToken extends Principal ...@@ -120,8 +139,10 @@ public abstract class SelfValidatingAuthenticationToken extends Principal
@Override @Override
public abstract String toString(); public abstract String toString();
/** Implementation specific version of a peppered checksum. */
public abstract String calcChecksum(String pepper); public abstract String calcChecksum(String pepper);
/** No credentials (returns null), since this token is self-validating. */
@Override @Override
public Object getCredentials() { public Object getCredentials() {
return null; return null;
...@@ -139,6 +160,9 @@ public abstract class SelfValidatingAuthenticationToken extends Principal ...@@ -139,6 +160,9 @@ public abstract class SelfValidatingAuthenticationToken extends Principal
return System.currentTimeMillis() >= getExpires(); return System.currentTimeMillis() >= getExpires();
} }
/**
* Test if the hash stored in `checksum` is equal to the one calculated using the secret pepper.
*/
public boolean isHashValid() { public boolean isHashValid() {
final String other = calcChecksum(); final String other = calcChecksum();
return this.checksum != null && this.checksum.equals(other); return this.checksum != null && this.checksum.equals(other);
...@@ -148,6 +172,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal ...@@ -148,6 +172,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal
return !isExpired() && isHashValid(); return !isExpired() && isHashValid();
} }
/** Return the hash (SHA512) of the stringified arguments. */
protected static String calcChecksum(final Object... fields) { protected static String calcChecksum(final Object... fields) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
for (final Object field : fields) { for (final Object field : fields) {
...@@ -167,6 +192,12 @@ public abstract class SelfValidatingAuthenticationToken extends Principal ...@@ -167,6 +192,12 @@ public abstract class SelfValidatingAuthenticationToken extends Principal
return ret; return ret;
} }
/**
* Parse a JSON string and return the generated token. Depending on the first element of the JSON,
* this is either (if it is "O") a OneTimeAuthenticationToken or else a SessionToken
*
* @todo Only allow "O" and "S"?
*/
public static SelfValidatingAuthenticationToken parse(String token) { public static SelfValidatingAuthenticationToken parse(String token) {
Object[] array = (Object[]) JSON.parse(token); Object[] array = (Object[]) JSON.parse(token);
switch (array[0].toString()) { switch (array[0].toString()) {
...@@ -177,6 +208,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal ...@@ -177,6 +208,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal
} }
} }
/** No "other" identity, so this returns itself. */
@Override @Override
public SelfValidatingAuthenticationToken getPrincipal() { public SelfValidatingAuthenticationToken getPrincipal() {
return this; return this;
......
...@@ -4,6 +4,9 @@ ...@@ -4,6 +4,9 @@
* *
* Copyright (C) 2018 Research Group Biomedical Physics, * Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
* Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
* Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
* Copyright (C) 2020 Daniel Hornung <d.hornung@indiscale.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
...@@ -27,6 +30,20 @@ import caosdb.server.ServerProperties; ...@@ -27,6 +30,20 @@ import caosdb.server.ServerProperties;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.eclipse.jetty.util.ajax.JSON; import org.eclipse.jetty.util.ajax.JSON;
/**
* Session tokens are formatted as JSON arrays with the following elements:
*
* <ul>
* <li>Anything but "O" (upper-case "o"), preferred is "S".
* <li>Realm
* <li>name within the Realm
* <li>list of roles
* <li>list of permissions
* <li>time of token generation (long, ms since 1970)
* <li>validity duration (long, ms)
* <li>salt
* <li>checksum
*/
public class SessionToken extends SelfValidatingAuthenticationToken { public class SessionToken extends SelfValidatingAuthenticationToken {
public SessionToken( public SessionToken(
...@@ -51,6 +68,7 @@ public class SessionToken extends SelfValidatingAuthenticationToken { ...@@ -51,6 +68,7 @@ public class SessionToken extends SelfValidatingAuthenticationToken {
private static final long serialVersionUID = 5887135104218573761L; private static final long serialVersionUID = 5887135104218573761L;
public static SessionToken parse(final Object[] array) { public static SessionToken parse(final Object[] array) {
// array[0] is not used here, it was already consumed to determine the type of token.
final Principal principal = new Principal((String) array[1], (String) array[2]); final Principal principal = new Principal((String) array[1], (String) array[2]);
final String[] roles = toStringArray((Object[]) array[3]); final String[] roles = toStringArray((Object[]) array[3]);
final String[] permissions = toStringArray((Object[]) array[4]); final String[] permissions = toStringArray((Object[]) array[4]);
...@@ -81,6 +99,7 @@ public class SessionToken extends SelfValidatingAuthenticationToken { ...@@ -81,6 +99,7 @@ public class SessionToken extends SelfValidatingAuthenticationToken {
return generate((Principal) subject.getPrincipal(), permissions, roles); return generate((Principal) subject.getPrincipal(), permissions, roles);
} }
/** Nothing to set in this implemention. */
@Override @Override
protected void setFields(Object[] fields) {} protected void setFields(Object[] fields) {}
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
* *
* Copyright (C) 2018 Research Group Biomedical Physics, * Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
* Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
* Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
...@@ -116,6 +118,8 @@ public class UserSources extends HashMap<String, UserSource> { ...@@ -116,6 +118,8 @@ public class UserSources extends HashMap<String, UserSource> {
} }
} }
private Ini map = null;
public UserSource put(final UserSource src) { public UserSource put(final UserSource src) {
if (src.getName() == null) { if (src.getName() == null) {
throw new IllegalArgumentException("A user source's name must not be null."); throw new IllegalArgumentException("A user source's name must not be null.");
...@@ -131,8 +135,6 @@ public class UserSources extends HashMap<String, UserSource> { ...@@ -131,8 +135,6 @@ public class UserSources extends HashMap<String, UserSource> {
return instance.put(src); return instance.put(src);
} }
private Ini map = null;
public void initMap() { public void initMap() {
this.map = new Ini(); this.map = new Ini();
try (final FileInputStream f = try (final FileInputStream f =
...@@ -147,6 +149,7 @@ public class UserSources extends HashMap<String, UserSource> { ...@@ -147,6 +149,7 @@ public class UserSources extends HashMap<String, UserSource> {
/** /**
* Return the roles of a given user. * Return the roles of a given user.
* *
* @todo Refactor name: resolveRoles(...)?
* @param realm * @param realm
* @param username * @param username
* @return * @return
...@@ -194,6 +197,7 @@ public class UserSources extends HashMap<String, UserSource> { ...@@ -194,6 +197,7 @@ public class UserSources extends HashMap<String, UserSource> {
return instance.map.getSectionProperty(Ini.DEFAULT_SECTION_NAME, KEY_DEFAULT_REALM, "CaosDB"); return instance.map.getSectionProperty(Ini.DEFAULT_SECTION_NAME, KEY_DEFAULT_REALM, "CaosDB");
} }
// @todo Refactor name: resolveRoles(...)?
public static Set<String> resolve(final Principal principal) { public static Set<String> resolve(final Principal principal) {
if (AnonymousAuthenticationToken.PRINCIPAL == principal) { if (AnonymousAuthenticationToken.PRINCIPAL == principal) {
// anymous has one role // anymous has one role
......
...@@ -208,6 +208,10 @@ public class ScriptingResource extends AbstractCaosDBServerResource { ...@@ -208,6 +208,10 @@ public class ScriptingResource extends AbstractCaosDBServerResource {
return AuthenticationUtils.isAnonymous(getUser()); return AuthenticationUtils.isAnonymous(getUser());
} }
/**
* Generate and return a token for the purpose of the given call. If the user is not anonymous and
* the call is not configured to be called by everyone, a SessionToken is returned instead.
*/
public Object generateAuthToken(String call) { public Object generateAuthToken(String call) {
String purpose = "scripting:" + call; String purpose = "scripting:" + call;
Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser()); Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser());
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
* *
* Copyright (C) 2018 Research Group Biomedical Physics, * Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
* Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
* Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
* *
* Copyright (C) 2018 Research Group Biomedical Physics, * Copyright (C) 2018 Research Group Biomedical Physics,
* Max-Planck-Institute for Dynamics and Self-Organization Göttingen * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
* Copyright (C) 2020 Indiscale GmbH <info@indiscale.com>
* Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
......
...@@ -26,9 +26,6 @@ import caosdb.server.entity.Message.MessageType; ...@@ -26,9 +26,6 @@ import caosdb.server.entity.Message.MessageType;
public class ServerMessages { public class ServerMessages {
public static final Message UNAUTHENTICATED =
new Message(MessageType.Error, 401, "Sign up, please!");
public static final Message ENTITY_HAS_BEEN_DELETED_SUCCESSFULLY = public static final Message ENTITY_HAS_BEEN_DELETED_SUCCESSFULLY =
new Message(MessageType.Info, 10, "This entity has been deleted successfully."); new Message(MessageType.Info, 10, "This entity has been deleted successfully.");
...@@ -258,6 +255,9 @@ public class ServerMessages { ...@@ -258,6 +255,9 @@ public class ServerMessages {
0, 0,
"User has been activated. You can now log in with your username and password."); "User has been activated. You can now log in with your username and password.");
public static final Message UNAUTHENTICATED =
new Message(MessageType.Error, 401, "Sign in, please.");
public static final Message AUTHORIZATION_ERROR = public static final Message AUTHORIZATION_ERROR =
new Message(MessageType.Error, 403, "You are not allowed to do this."); new Message(MessageType.Error, 403, "You are not allowed to do this.");
......
...@@ -187,8 +187,8 @@ public class Utils { ...@@ -187,8 +187,8 @@ public class Utils {
/** /**
* Generate a secure filename (base32 letters and numbers). * Generate a secure filename (base32 letters and numbers).
* *
* <p>Very similar to getUID, but uses cryptographic random number instead, also also nicely * <p>Very similar to getUID, but uses cryptographic random number instead, also nicely formats
* formats the resulting string. * the resulting string.
* *
* @param byteSize How many bytes of random bits shall be generated. * @param byteSize How many bytes of random bits shall be generated.
* @return The filename as a String. * @return The filename as a String.
......
...@@ -161,7 +161,7 @@ public class AuthTokenTest { ...@@ -161,7 +161,7 @@ public class AuthTokenTest {
new String[] {"roles"}, new String[] {"roles"},
1L, 1L,
3000L); 3000L);
Assert.assertEquals(1L, t1.getMaxAttempts()); Assert.assertEquals(1L, t1.getMaxReplays());
Assert.assertFalse(t1.isExpired()); Assert.assertFalse(t1.isExpired());
Assert.assertTrue(t1.isHashValid()); Assert.assertTrue(t1.isHashValid());
Assert.assertTrue(t1.isValid()); Assert.assertTrue(t1.isValid());
...@@ -172,7 +172,7 @@ public class AuthTokenTest { ...@@ -172,7 +172,7 @@ public class AuthTokenTest {
Assert.assertEquals(t1, parsed); Assert.assertEquals(t1, parsed);
Assert.assertEquals(serialized, parsed.toString()); Assert.assertEquals(serialized, parsed.toString());
Assert.assertEquals(1L, t1.getMaxAttempts()); Assert.assertEquals(1L, t1.getMaxReplays());
Assert.assertFalse(parsed.isExpired()); Assert.assertFalse(parsed.isExpired());
Assert.assertTrue(parsed.isHashValid()); Assert.assertTrue(parsed.isHashValid());
Assert.assertTrue(parsed.isValid()); Assert.assertTrue(parsed.isValid());
...@@ -263,7 +263,7 @@ public class AuthTokenTest { ...@@ -263,7 +263,7 @@ public class AuthTokenTest {
Integer.parseInt( Integer.parseInt(
CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS)), CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS)),
config.getTimeout()); config.getTimeout());
Assert.assertEquals(1, config.getMaxAttempts()); Assert.assertEquals(1, config.getMaxReplays());
Assert.assertNull("no purpose", config.getPurpose()); Assert.assertNull("no purpose", config.getPurpose());
Assert.assertArrayEquals("no permissions", new String[] {}, config.getPermissions()); Assert.assertArrayEquals("no permissions", new String[] {}, config.getPermissions());
...@@ -277,7 +277,7 @@ public class AuthTokenTest { ...@@ -277,7 +277,7 @@ public class AuthTokenTest {
testYaml.append("purpose: test purpose 1\n"); testYaml.append("purpose: test purpose 1\n");
testYaml.append("roles: [ role1, \"role2\"]\n"); testYaml.append("roles: [ role1, \"role2\"]\n");
testYaml.append("expiresAfterSeconds: 10\n"); testYaml.append("expiresAfterSeconds: 10\n");
testYaml.append("maxAttempts: 3\n"); testYaml.append("maxReplays: 3\n");
testYaml.append("permissions:\n"); testYaml.append("permissions:\n");
testYaml.append(" - permission1\n"); testYaml.append(" - permission1\n");
testYaml.append(" - 'permission2'\n"); testYaml.append(" - 'permission2'\n");
...@@ -290,7 +290,7 @@ public class AuthTokenTest { ...@@ -290,7 +290,7 @@ public class AuthTokenTest {
Assert.assertTrue("parsing to Config object", map.get("test purpose 1") instanceof Config); Assert.assertTrue("parsing to Config object", map.get("test purpose 1") instanceof Config);
Config config = map.get("test purpose 1"); Config config = map.get("test purpose 1");
Assert.assertEquals(10000, config.getTimeout()); Assert.assertEquals(10000, config.getTimeout());
Assert.assertEquals(3, config.getMaxAttempts()); Assert.assertEquals(3, config.getMaxReplays());
Assert.assertArrayEquals( Assert.assertArrayEquals(
"permissions parsed", "permissions parsed",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment