diff --git a/conf/core/authtoken.example.yaml b/conf/core/authtoken.example.yaml index d0628842f2b6a8c1037156b286f1eab094ce4499..37d11733d7ffee798e0e1491974703201e8b351b 100644 --- a/conf/core/authtoken.example.yaml +++ b/conf/core/authtoken.example.yaml @@ -12,7 +12,7 @@ output: file: "authtoken/admin_token_3_attempts.json" schedule: "0/10 * * ? * * *" - maxAttempts: 3 + maxReplays: 3 - roles: - administration output: diff --git a/conf/core/server.conf b/conf/core/server.conf index ed08510ea7187f82b62bc5eba84cd586af1b5706..d30d82b681a185a820783c6c2d8d81a62981eb64 100644 --- a/conf/core/server.conf +++ b/conf/core/server.conf @@ -123,11 +123,12 @@ ONE_TIME_TOKEN_EXPIRES_MS=604800000 # Path to config file for one time tokens, for example authtoken.yml. AUTHTOKEN_CONFIG= -# Timeout after which a consumed one-time token expires regardless of the -# maximum of attempts that are allowed for that token. This is only a default -# value. The actual timeout of tokens can be configured otherwise. +# Timeout after which a one-time token expires once it has been first consumed, +# regardless of the maximum of replays that are allowed for that token. This is +# only a default value. The actual timeout of tokens can be configured +# otherwise. # 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" WEBUI_HTTP_HEADER_CACHE_MAX_AGE=28800 diff --git a/src/main/java/caosdb/server/CaosDBServer.java b/src/main/java/caosdb/server/CaosDBServer.java index 88beecfbc3d437ab75d121c60a8105c2bbe3107a..45da4eb0e655f011e06deede49eec2035745fac1 100644 --- a/src/main/java/caosdb/server/CaosDBServer.java +++ b/src/main/java/caosdb/server/CaosDBServer.java @@ -123,6 +123,9 @@ public class CaosDBServer extends Application { private static ArrayList<Runnable> preShutdownHooks = new ArrayList<Runnable>(); private static boolean START_BACKEND = true; 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) { return getServerProperties().getProperty(key); @@ -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. * @@ -580,6 +579,7 @@ public class CaosDBServer extends Application { private void setSessionCookies(final Response response) { final Subject subject = SecurityUtils.getSubject(); + // if authenticated as a normal user: generate and set session cookie. if (subject.isAuthenticated() && subject.getPrincipal() != AnonymousAuthenticationToken.PRINCIPAL) { final SessionToken sessionToken = SessionToken.generate(subject); diff --git a/src/main/java/caosdb/server/ServerProperties.java b/src/main/java/caosdb/server/ServerProperties.java index 6ac7f091fd4670c9e16b55f443f59518d69d8a05..29ba470ddbcad0e79db210917234562c4b58620a 100644 --- a/src/main/java/caosdb/server/ServerProperties.java +++ b/src/main/java/caosdb/server/ServerProperties.java @@ -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_ONE_TIME_TOKEN_EXPIRES_MS = "ONE_TIME_TOKEN_EXPIRES_MS"; - public static final String KEY_ONE_TIME_TOKEN_ATTEMPTS_TIMEOUT_MS = - "ONE_TIME_TOKEN_ATTEMPTS_TIMEOUT_MS"; + public static final String KEY_ONE_TIME_TOKEN_REPLAYS_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_DISABLE = "CACHE_DISABLE"; diff --git a/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java b/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java index bc78d6df6116c40fc551f53313b11cbf25faff9a..c3576031da2d9a598bd74a07fe12df62ed7de90d 100644 --- a/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java +++ b/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * 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 * it under the terms of the GNU Affero General Public License as @@ -124,9 +126,7 @@ public class AuthenticationUtils { if (token != null && token.isValid()) { t = new Timestamp(token.getExpires()).toString().replaceFirst(" ", "T"); - exp_in_sec = (int) Math.ceil(token.getTimeout() / 1000.0); // new - // expiration - // time. + exp_in_sec = (int) Math.ceil(token.getTimeout() / 1000.0); // new expiration time return new CookieSetting( 0, AuthenticationUtils.SESSION_TIMEOUT_COOKIE, diff --git a/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java b/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java index be557e49c5aef0bc42f548016549c44dc0202e39..abd52d9ed306f43c51d8bf52bdf1c25d86d8454e 100644 --- a/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java +++ b/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java @@ -49,6 +49,7 @@ public class CaosDBAuthorizingRealm extends AuthorizingRealm { final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo(); Object principal = principals.getPrimaryPrincipal(); + // Add explicitly given roles and permissions. if (principal instanceof SelfValidatingAuthenticationToken) { Collection<String> sessionPermissions = getSessionPermissions((SelfValidatingAuthenticationToken) principal); @@ -60,8 +61,7 @@ public class CaosDBAuthorizingRealm extends AuthorizingRealm { authzInfo.addStringPermissions(sessionPermissions); } - // find all roles which are associated with this principal in this - // realm. + // Find all roles which are associated with this principal in this realm. final Set<String> principalRoles = UserSources.resolve((Principal) principals.getPrimaryPrincipal()); if (principalRoles != null) { diff --git a/src/main/java/caosdb/server/accessControl/CaosDBRolePermissionResolver.java b/src/main/java/caosdb/server/accessControl/CaosDBRolePermissionResolver.java index 3350a68700f108e8ed5cf7309e9f305ac0bb669b..ae4c38605f07f5222499e076def3a3d48c31a385 100644 --- a/src/main/java/caosdb/server/accessControl/CaosDBRolePermissionResolver.java +++ b/src/main/java/caosdb/server/accessControl/CaosDBRolePermissionResolver.java @@ -33,6 +33,7 @@ import org.apache.shiro.authc.AuthenticationException; public class CaosDBRolePermissionResolver { + /** Return CaosPermission with the rules which are associated with the roles. */ public CaosPermission resolvePermissionsInRole(final Set<String> roles) { final HashSet<PermissionRule> rules = new HashSet<PermissionRule>(); for (final String role : roles) { diff --git a/src/main/java/caosdb/server/accessControl/Config.java b/src/main/java/caosdb/server/accessControl/Config.java index d433a1e90df035e805e46cf5452c1330811ce061..27f35f692cad23c2e03e4bab01b90d6e01b5ab97 100644 --- a/src/main/java/caosdb/server/accessControl/Config.java +++ b/src/main/java/caosdb/server/accessControl/Config.java @@ -5,9 +5,9 @@ public class Config { private String[] roles = {}; private String purpose = null; private OneTimeTokenToFile output = null; - private int maxAttempts = 1; + private int maxReplays = 1; 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(); public Config() {} @@ -28,20 +28,20 @@ public class Config { this.timeout = timeout; } - public void setAttemptsTimeoutSeconds(int seconds) { - this.setAttemptsTimeout(seconds * 1000); + public void setReplaysTimeoutSeconds(int seconds) { + this.setReplaysTimeout(seconds * 1000); } public void setExpiresAfterSeconds(int seconds) { this.setTimeout(seconds * 1000); } - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; + public void setMaxReplays(int maxReplays) { + this.maxReplays = maxReplays; } - public int getMaxAttempts() { - return maxAttempts; + public int getMaxReplays() { + return maxReplays; } public String[] getPermissions() { @@ -76,11 +76,11 @@ public class Config { this.output = output; } - public int getAttemptsTimeout() { - return attemptsTimeout; + public int getReplaysTimeout() { + return replaysTimeout; } - public void setAttemptsTimeout(int attemptsTimeout) { - this.attemptsTimeout = attemptsTimeout; + public void setReplaysTimeout(int replaysTimeout) { + this.replaysTimeout = replaysTimeout; } } diff --git a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java index e211668c8a83ae4dc13addc19406fbc486646c31..6929c41cdd25569839a85bd512909c43f6fc683a 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * 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 * it under the terms of the GNU Affero General Public License as @@ -43,18 +45,18 @@ import org.slf4j.LoggerFactory; public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToken { - public static final long DEFAULT_MAX_ATTEMPTS = 1L; - public static final int DEFAULT_ATTEMPTS_TIMEOUT_MS = + public static final long DEFAULT_MAX_REPLAYS = 1L; + public static final int DEFAULT_REPLAYS_TIMEOUT_MS = 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 = Integer.parseInt( CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS)); public static final String REALM_NAME = "OneTimeAuthenticationToken"; // TODO move to UserSources public static final Logger LOGGER = LoggerFactory.getLogger(OneTimeAuthenticationToken.class); - private long maxAttempts; - private long attemptsTimeout; + private long maxReplays; + private long replaysTimeout; public OneTimeAuthenticationToken( final Principal principal, @@ -64,16 +66,16 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final String checksum, final String[] permissions, final String[] roles, - final long maxAttempts, - final long attemptsTimeout) { + final long maxReplays, + final long replaysTimeout) { super(principal, date, timeout, salt, checksum, permissions, roles); - this.attemptsTimeout = attemptsTimeout; - this.maxAttempts = maxAttempts; + this.replaysTimeout = replaysTimeout; + this.maxReplays = maxReplays; consume(); } - public long getAttemptsTimeout() { - return attemptsTimeout; + public long getReplaysTimeout() { + return replaysTimeout; } public void consume() { @@ -85,15 +87,15 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final long timeout, final String[] permissions, final String[] roles, - final Long maxAttempts, - final Long attemptsTimeout) { + final Long maxReplays, + final Long replaysTimeout) { super( principal, timeout, permissions, roles, - defaultIfNull(maxAttempts, DEFAULT_MAX_ATTEMPTS), - defaultIfNull(attemptsTimeout, DEFAULT_ATTEMPTS_TIMEOUT_MS)); + defaultIfNull(maxReplays, DEFAULT_MAX_REPLAYS), + defaultIfNull(replaysTimeout, DEFAULT_REPLAYS_TIMEOUT_MS)); } private static final long serialVersionUID = -1072740888045267613L; @@ -113,10 +115,10 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final long timeout = (Long) array[6]; final String salt = (String) array[7]; final String checksum = (String) array[8]; - final long maxAttempts = (Long) array[9]; - final long attemptsTimeout = (Long) array[10]; + final long maxReplays = (Long) array[9]; + final long replaysTimeout = (Long) array[10]; 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( @@ -124,11 +126,11 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final String[] permissions, final String[] roles, final long timeout, - final long maxAttempts, - final long attemptsTimeout) { + final long maxReplays, + final long replaysTimeout) { return new OneTimeAuthenticationToken( - principal, timeout, permissions, roles, maxAttempts, attemptsTimeout); + principal, timeout, permissions, roles, maxReplays, replaysTimeout); } public static List<Config> loadConfig(InputStream input) throws Exception { @@ -174,8 +176,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke c.getPermissions(), c.getRoles(), c.getTimeout(), - c.getMaxAttempts(), - c.getAttemptsTimeout()); + c.getMaxReplays(), + c.getReplaysTimeout()); } static Map<String, Config> purposes = new HashMap<>(); @@ -212,8 +214,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke @Override protected void setFields(Object[] fields) { if (fields.length == 2) { - this.maxAttempts = (long) fields[0]; - this.attemptsTimeout = (long) fields[1]; + this.maxReplays = (long) fields[0]; + this.replaysTimeout = (long) fields[1]; } else { throw new InstantiationError("Too few fields."); } @@ -230,8 +232,8 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke this.salt, calcChecksum((Object[]) this.permissions), calcChecksum((Object[]) this.roles), - this.maxAttempts, - this.attemptsTimeout, + this.maxReplays, + this.replaysTimeout, pepper); } @@ -248,13 +250,13 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke this.timeout, this.salt, this.checksum, - this.maxAttempts, - this.attemptsTimeout + this.maxReplays, + this.replaysTimeout }); } - public long getMaxAttempts() { - return maxAttempts; + public long getMaxReplays() { + return maxReplays; } public static void resetConfig() { diff --git a/src/main/java/caosdb/server/accessControl/OneTimeTokenConsumedInfo.java b/src/main/java/caosdb/server/accessControl/OneTimeTokenConsumedInfo.java index a7122c2b28ed38baaf60c8da6b404b1fcf97c349..a8ebd2c807712d8b06ec68771e7abb8082ce0696 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeTokenConsumedInfo.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeTokenConsumedInfo.java @@ -7,6 +7,10 @@ import java.util.List; import java.util.Map; 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 { private static Map<String, OneTimeTokenConsumedInfo> consumedOneTimeTokens = new HashMap<>(); @@ -24,6 +28,7 @@ class OneTimeTokenConsumedInfo { } } + /** If the token is valid, consume it once and store this information. */ public static void consume(OneTimeAuthenticationToken oneTimeAuthenticationToken) { if (oneTimeAuthenticationToken.isValid()) { String key = OneTimeTokenConsumedInfo.getKey(oneTimeAuthenticationToken); @@ -40,7 +45,7 @@ class OneTimeTokenConsumedInfo { } private OneTimeAuthenticationToken oneTimeAuthenticationToken; - private List<Long> attempts = new LinkedList<>(); + private List<Long> replays = new LinkedList<>(); public OneTimeTokenConsumedInfo(OneTimeAuthenticationToken oneTimeAuthenticationToken) { this.oneTimeAuthenticationToken = oneTimeAuthenticationToken; @@ -50,30 +55,31 @@ class OneTimeTokenConsumedInfo { return token.checksum; } - private int getNoOfAttempts() { - return attempts.size(); + private int getNoOfReplays() { + return replays.size(); } - private long getMaxAttempts() { - return oneTimeAuthenticationToken.getMaxAttempts(); + private long getMaxReplays() { + return oneTimeAuthenticationToken.getMaxReplays(); } - private long getAttemptTimeout() { - if (attempts.size() == 0) { + private long getReplayTimeout() { + if (replays.size() == 0) { return Long.MAX_VALUE; } - long firstAttemptTime = attempts.get(0); - return firstAttemptTime + oneTimeAuthenticationToken.getAttemptsTimeout(); + long firstReplayTime = replays.get(0); + return firstReplayTime + oneTimeAuthenticationToken.getReplaysTimeout(); } + /** If there are still replays and time left, increase the replay counter by one. */ public void consume() { - synchronized (attempts) { - if (getNoOfAttempts() >= getMaxAttempts()) { + synchronized (replays) { + if (getNoOfReplays() >= getMaxReplays()) { throw new AuthenticationException("One-time token was consumed too often."); - } else if (getAttemptTimeout() < System.currentTimeMillis()) { - throw new AuthenticationException("One-time token attempts timeout expired."); + } else if (getReplayTimeout() < System.currentTimeMillis()) { + throw new AuthenticationException("One-time token replays timeout expired."); } - attempts.add(System.currentTimeMillis()); + replays.add(System.currentTimeMillis()); } } diff --git a/src/main/java/caosdb/server/accessControl/OneTimeTokenToFile.java b/src/main/java/caosdb/server/accessControl/OneTimeTokenToFile.java index a91dcfa15862f94566ee9da53babbf99ba000e53..b4006783a2105a353749edfaa475bde3dae93808 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeTokenToFile.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeTokenToFile.java @@ -49,10 +49,11 @@ public class OneTimeTokenToFile implements Job { 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 { if (this.schedule != null) { - OneTimeAuthenticationToken.generate(config); // test config + OneTimeAuthenticationToken.generate(config); // test config, throw away token JobDataMap map = new JobDataMap(); map.put("config", config); map.put("file", file); diff --git a/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java index e65b70efb407ea211f9ed0d5f516a7d17c05eb12..684227384da07555055b8fa3ba3e46bcd0fc26ff 100644 --- a/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * 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 * it under the terms of the GNU Affero General Public License as @@ -28,12 +30,28 @@ import java.util.Collection; import org.apache.shiro.authc.AuthenticationToken; 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 implements AuthenticationToken { protected static final transient String PEPPER = Utils.getSecureFilename(32); private static final long serialVersionUID = -7212039848895531161L; + // date is the token creation time, in ms since 1970 protected final long date; + // token validity duration protected final long timeout; protected final String checksum; protected final String salt; @@ -93,6 +111,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal this.checksum = checksum == null && newChecksum ? calcChecksum() : checksum; } + /** Customizable customization method, will be called with the remaining constructor arguments. */ protected abstract void setFields(Object[] fields); public SelfValidatingAuthenticationToken( @@ -120,8 +139,10 @@ public abstract class SelfValidatingAuthenticationToken extends Principal @Override public abstract String toString(); + /** Implementation specific version of a peppered checksum. */ public abstract String calcChecksum(String pepper); + /** No credentials (returns null), since this token is self-validating. */ @Override public Object getCredentials() { return null; @@ -139,6 +160,9 @@ public abstract class SelfValidatingAuthenticationToken extends Principal return System.currentTimeMillis() >= getExpires(); } + /** + * Test if the hash stored in `checksum` is equal to the one calculated using the secret pepper. + */ public boolean isHashValid() { final String other = calcChecksum(); return this.checksum != null && this.checksum.equals(other); @@ -148,6 +172,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal return !isExpired() && isHashValid(); } + /** Return the hash (SHA512) of the stringified arguments. */ protected static String calcChecksum(final Object... fields) { final StringBuilder sb = new StringBuilder(); for (final Object field : fields) { @@ -167,6 +192,12 @@ public abstract class SelfValidatingAuthenticationToken extends Principal 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) { Object[] array = (Object[]) JSON.parse(token); switch (array[0].toString()) { @@ -177,6 +208,7 @@ public abstract class SelfValidatingAuthenticationToken extends Principal } } + /** No "other" identity, so this returns itself. */ @Override public SelfValidatingAuthenticationToken getPrincipal() { return this; diff --git a/src/main/java/caosdb/server/accessControl/SessionToken.java b/src/main/java/caosdb/server/accessControl/SessionToken.java index 2e82e3553497e56e4578f45a7768a9f7a5c71b77..d24b4afeb739d764947092783c544168a6c0cf3f 100644 --- a/src/main/java/caosdb/server/accessControl/SessionToken.java +++ b/src/main/java/caosdb/server/accessControl/SessionToken.java @@ -4,6 +4,9 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * 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 * it under the terms of the GNU Affero General Public License as @@ -27,6 +30,20 @@ import caosdb.server.ServerProperties; import org.apache.shiro.subject.Subject; 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 SessionToken( @@ -51,6 +68,7 @@ public class SessionToken extends SelfValidatingAuthenticationToken { private static final long serialVersionUID = 5887135104218573761L; 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 String[] roles = toStringArray((Object[]) array[3]); final String[] permissions = toStringArray((Object[]) array[4]); @@ -81,6 +99,7 @@ public class SessionToken extends SelfValidatingAuthenticationToken { return generate((Principal) subject.getPrincipal(), permissions, roles); } + /** Nothing to set in this implemention. */ @Override protected void setFields(Object[] fields) {} diff --git a/src/main/java/caosdb/server/accessControl/UserSources.java b/src/main/java/caosdb/server/accessControl/UserSources.java index e09f7c242429140400ff9d3946ab88991021202d..2aae14faddb3a35308ba6982a426db5e6e2e9b8f 100644 --- a/src/main/java/caosdb/server/accessControl/UserSources.java +++ b/src/main/java/caosdb/server/accessControl/UserSources.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * 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 * it under the terms of the GNU Affero General Public License as @@ -116,6 +118,8 @@ public class UserSources extends HashMap<String, UserSource> { } } + private Ini map = null; + public UserSource put(final UserSource src) { if (src.getName() == null) { throw new IllegalArgumentException("A user source's name must not be null."); @@ -131,8 +135,6 @@ public class UserSources extends HashMap<String, UserSource> { return instance.put(src); } - private Ini map = null; - public void initMap() { this.map = new Ini(); try (final FileInputStream f = @@ -147,6 +149,7 @@ public class UserSources extends HashMap<String, UserSource> { /** * Return the roles of a given user. * + * @todo Refactor name: resolveRoles(...)? * @param realm * @param username * @return @@ -194,6 +197,7 @@ public class UserSources extends HashMap<String, UserSource> { return instance.map.getSectionProperty(Ini.DEFAULT_SECTION_NAME, KEY_DEFAULT_REALM, "CaosDB"); } + // @todo Refactor name: resolveRoles(...)? public static Set<String> resolve(final Principal principal) { if (AnonymousAuthenticationToken.PRINCIPAL == principal) { // anymous has one role diff --git a/src/main/java/caosdb/server/resource/ScriptingResource.java b/src/main/java/caosdb/server/resource/ScriptingResource.java index d3827b4544beb16fd9bbaf37dad795be0e36b129..4f9ef958e676a9f7e743d6820b7df7c41120f7db 100644 --- a/src/main/java/caosdb/server/resource/ScriptingResource.java +++ b/src/main/java/caosdb/server/resource/ScriptingResource.java @@ -208,6 +208,10 @@ public class ScriptingResource extends AbstractCaosDBServerResource { 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) { String purpose = "scripting:" + call; Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser()); diff --git a/src/main/java/caosdb/server/transaction/Transaction.java b/src/main/java/caosdb/server/transaction/Transaction.java index 99161894234214bff84d67fe6ded0af1d2ae4767..d2c16438b5c071fec72b11b600604b46c4b2da30 100644 --- a/src/main/java/caosdb/server/transaction/Transaction.java +++ b/src/main/java/caosdb/server/transaction/Transaction.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * 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 * it under the terms of the GNU Affero General Public License as diff --git a/src/main/java/caosdb/server/utils/Initialization.java b/src/main/java/caosdb/server/utils/Initialization.java index e6d0662e525d920a5496a042b04213c86c5e8c0a..9f77fcf2992c0e7fb8de38ee334bebe8c93ef3d8 100644 --- a/src/main/java/caosdb/server/utils/Initialization.java +++ b/src/main/java/caosdb/server/utils/Initialization.java @@ -4,6 +4,8 @@ * * Copyright (C) 2018 Research Group Biomedical Physics, * 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 * it under the terms of the GNU Affero General Public License as diff --git a/src/main/java/caosdb/server/utils/ServerMessages.java b/src/main/java/caosdb/server/utils/ServerMessages.java index a77ad38c121ccf43b8042f83312767caaf3922ca..29983924246f5bc5987ffdbfeaf949d0946c7f16 100644 --- a/src/main/java/caosdb/server/utils/ServerMessages.java +++ b/src/main/java/caosdb/server/utils/ServerMessages.java @@ -26,9 +26,6 @@ import caosdb.server.entity.Message.MessageType; 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 = new Message(MessageType.Info, 10, "This entity has been deleted successfully."); @@ -258,6 +255,9 @@ public class ServerMessages { 0, "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 = new Message(MessageType.Error, 403, "You are not allowed to do this."); diff --git a/src/main/java/caosdb/server/utils/Utils.java b/src/main/java/caosdb/server/utils/Utils.java index d5bb0903de581597b23261e0bb87caa1e9ea7829..085be51d0929ce76d10ed5e4e8c32696e0b48476 100644 --- a/src/main/java/caosdb/server/utils/Utils.java +++ b/src/main/java/caosdb/server/utils/Utils.java @@ -187,8 +187,8 @@ public class Utils { /** * Generate a secure filename (base32 letters and numbers). * - * <p>Very similar to getUID, but uses cryptographic random number instead, also also nicely - * formats the resulting string. + * <p>Very similar to getUID, but uses cryptographic random number instead, also nicely formats + * the resulting string. * * @param byteSize How many bytes of random bits shall be generated. * @return The filename as a String. diff --git a/src/test/java/caosdb/server/authentication/AuthTokenTest.java b/src/test/java/caosdb/server/authentication/AuthTokenTest.java index b8b1da51cc78202f2e487662bc6a8260df4fa61b..7fb22fe96fb6d8140a9834d5cc2061d48bb8d637 100644 --- a/src/test/java/caosdb/server/authentication/AuthTokenTest.java +++ b/src/test/java/caosdb/server/authentication/AuthTokenTest.java @@ -161,7 +161,7 @@ public class AuthTokenTest { new String[] {"roles"}, 1L, 3000L); - Assert.assertEquals(1L, t1.getMaxAttempts()); + Assert.assertEquals(1L, t1.getMaxReplays()); Assert.assertFalse(t1.isExpired()); Assert.assertTrue(t1.isHashValid()); Assert.assertTrue(t1.isValid()); @@ -172,7 +172,7 @@ public class AuthTokenTest { Assert.assertEquals(t1, parsed); Assert.assertEquals(serialized, parsed.toString()); - Assert.assertEquals(1L, t1.getMaxAttempts()); + Assert.assertEquals(1L, t1.getMaxReplays()); Assert.assertFalse(parsed.isExpired()); Assert.assertTrue(parsed.isHashValid()); Assert.assertTrue(parsed.isValid()); @@ -263,7 +263,7 @@ public class AuthTokenTest { Integer.parseInt( CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS)), config.getTimeout()); - Assert.assertEquals(1, config.getMaxAttempts()); + Assert.assertEquals(1, config.getMaxReplays()); Assert.assertNull("no purpose", config.getPurpose()); Assert.assertArrayEquals("no permissions", new String[] {}, config.getPermissions()); @@ -277,7 +277,7 @@ public class AuthTokenTest { testYaml.append("purpose: test purpose 1\n"); testYaml.append("roles: [ role1, \"role2\"]\n"); testYaml.append("expiresAfterSeconds: 10\n"); - testYaml.append("maxAttempts: 3\n"); + testYaml.append("maxReplays: 3\n"); testYaml.append("permissions:\n"); testYaml.append(" - permission1\n"); testYaml.append(" - 'permission2'\n"); @@ -290,7 +290,7 @@ public class AuthTokenTest { Assert.assertTrue("parsing to Config object", map.get("test purpose 1") instanceof Config); Config config = map.get("test purpose 1"); Assert.assertEquals(10000, config.getTimeout()); - Assert.assertEquals(3, config.getMaxAttempts()); + Assert.assertEquals(3, config.getMaxReplays()); Assert.assertArrayEquals( "permissions parsed",