diff --git a/conf/core/authtoken.example.yaml b/conf/core/authtoken.example.yaml index 37d11733d7ffee798e0e1491974703201e8b351b..5b5eb1abbd65eb90c0c4ec93e6fda72a84da7982 100644 --- a/conf/core/authtoken.example.yaml +++ b/conf/core/authtoken.example.yaml @@ -1,26 +1,100 @@ -# TODO: Add documentation. -- purpose: scripting:administration/diagnostics.py +# OneTimeAuthenticationToken Config +# +# One-Time Authentication (OTA) Tokens are a means to authenticate a client without a +# password. +# +# This example config file illustrates several use cases of One-Time +# Authentication Tokens. +# +# This yaml file contains an array of configuration objects which may have the +# properties that are defined by the caosdb.server.accessControl.Config class. +# +# These properties are: +# +# - expiresAfter:: An integer timespan in milliseconds after the OTA Token was +# generated by the server when the token expires. So the token will not be +# valid after <date> + <expiresAfter>. +# - expiresAfterSeconds:: A convenient option which has the same meaning as +# expiresAfter but with seconds instead of milliseconds. +# - roles:: A list of strings which are the user roles that the client has when +# it authenticates with this token. This is a way to give a client a set of +# roles which it wouldn't have otherwise. Subsequently, the client also has +# all permissions of its roles. +# - permissions:: A list of string which are the permissions that the client has +# when it authenticates with this token. This is another way to give a +# client permissions which it wouldn't have otherwise. +# - maxReplays:: Normally a One-Time token can be used exactly once and +# invalidates the moment when the server recognizes that a client uses it. +# However, when a network connection is unreliable, it should be possible to +# attempt another login with the same token for a few times. And in another +# use case, it might be practical to authenticate several clients at the +# same time with the same authentication token. The maxReplays value is the +# number of times that a server accepts an OTA token as valid. The default +# is, of course, 1. +# - replayTimeout:: An integer timespan in milliseconds. Because replays are a +# possible attack vector, the time frame in which the same token might be is +# limitted by this value. The default value is configured by the server +# property 'ONE_TIME_TOKEN_REPLAYS_TIMEOUT_MS' which has a default of 30000, +# which is 30 seconds. +# - replayTimeoutSeconds:: A convenient option which has the same meaning as +# replayTimeout but with seconds instead of milliseconds. +# - output:: Defines how the OTA token is outputted by the server. If this +# property is not present the OTA is not outputted in any way but only used +# internally (see purpose). The 'output' object has the following properties: +# - file:: An absolute or relative path in the servers file system. This +# property means that the OTA is written to that file on server start. +# - schedule:: A cron-tab compliant string indicating when the OTA is +# renewed and the file is being overridden with the new OTA. +# - purpose:: A string which is used (only internally so far) to generate an OTA +# for a special purpose, e.g. the execution of a server-side script with +# particular permissions. This way, an otherwise unprivileged client might +# execute a server-side script with all necessary permissions, if only the +# client has the "SCRIPTING:EXECUTE:<script-path>" permission and this +# config file has a configuration with +# "purpose: SCRIPTING:EXECUTE:<script-path>" (case-sensitive). +# +# +# Examples: +# +# 1. Every client with the SCRIPTING:EXECUTE:administration/diagnostics.py +# permission can execute the administration/diagnostics.py script with the +# administration role. +- purpose: SCRIPTING:EXECUTE:administration/diagnostics.py roles: - administration +# 2. The server writes an OTA token with the administration role to +# "authtoken/admin_token.txt" and refreshes that token every 10 seconds. - roles: - administration output: - file: "authtoken/admin_token.json" + file: "authtoken/admin_token.txt" schedule: "0/10 * * ? * * *" + +# 3. The server writes an OTA token with the administration role to +# "authtoken/admin_token_3_attempts.txt" which can be replayed 3 times in 10 +# seconds. The OTA token is refreshed every 10 seconds. - roles: - administration output: - file: "authtoken/admin_token_3_attempts.json" + file: "authtoken/admin_token_3_attempts.txt" schedule: "0/10 * * ? * * *" maxReplays: 3 + replayTimeout: 10000 + +# 4. The server writes an OTA token with the administration role to +# "authtoken/admin_token_expired.txt" which expires immediately. Of course +# this is onyl useful for testing. - roles: - administration output: - file: "authtoken/admin_token_expired.json" + file: "authtoken/admin_token_expired.txt" expiresAfterSeconds: 0 + + +# 5. The server writes an OTA token with the administration role to +# "authtoken/admin_token_crud.txt" and refreshes that token every 10 seconds. - roles: - administration output: - file: "authtoken/admin_token_crud.json" + file: "authtoken/admin_token_crud.txt" schedule: "0/10 * * ? * * *" - diff --git a/src/main/java/caosdb/server/accessControl/Config.java b/src/main/java/caosdb/server/accessControl/Config.java index 27f35f692cad23c2e03e4bab01b90d6e01b5ab97..3da7a426a9dcecfb93264cb9e504a157b10659c8 100644 --- a/src/main/java/caosdb/server/accessControl/Config.java +++ b/src/main/java/caosdb/server/accessControl/Config.java @@ -6,8 +6,8 @@ public class Config { private String purpose = null; private OneTimeTokenToFile output = null; private int maxReplays = 1; - private int timeout = OneTimeAuthenticationToken.DEFAULT_TIMEOUT_MS; - private int replaysTimeout = OneTimeAuthenticationToken.DEFAULT_REPLAYS_TIMEOUT_MS; + private long expiresAfter = OneTimeAuthenticationToken.DEFAULT_TIMEOUT_MS; + private long replayTimeout = OneTimeAuthenticationToken.DEFAULT_REPLAYS_TIMEOUT_MS; private String name = AnonymousAuthenticationToken.PRINCIPAL.getUsername(); public Config() {} @@ -20,20 +20,20 @@ public class Config { this.name = name; } - public int getTimeout() { - return timeout; + public long getExpiresAfter() { + return expiresAfter; } - public void setTimeout(int timeout) { - this.timeout = timeout; + public void setExpiresAfter(long timeout) { + this.expiresAfter = timeout; } - public void setReplaysTimeoutSeconds(int seconds) { - this.setReplaysTimeout(seconds * 1000); + public void setReplayTimeoutSeconds(long seconds) { + this.setReplayTimeout(seconds * 1000); } - public void setExpiresAfterSeconds(int seconds) { - this.setTimeout(seconds * 1000); + public void setExpiresAfterSeconds(long seconds) { + this.setExpiresAfter(seconds * 1000); } public void setMaxReplays(int maxReplays) { @@ -76,11 +76,11 @@ public class Config { this.output = output; } - public int getReplaysTimeout() { - return replaysTimeout; + public long getReplayTimeout() { + return replayTimeout; } - public void setReplaysTimeout(int replaysTimeout) { - this.replaysTimeout = replaysTimeout; + public void setReplayTimeout(long replaysTimeout) { + this.replayTimeout = replaysTimeout; } } diff --git a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java index 6929c41cdd25569839a85bd512909c43f6fc683a..8dddf77b092cb951695ecaa15191856a3909793f 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java @@ -175,9 +175,9 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke principal, c.getPermissions(), c.getRoles(), - c.getTimeout(), + c.getExpiresAfter(), c.getMaxReplays(), - c.getReplaysTimeout()); + c.getReplayTimeout()); } static Map<String, Config> purposes = new HashMap<>(); diff --git a/src/main/java/caosdb/server/resource/ScriptingResource.java b/src/main/java/caosdb/server/resource/ScriptingResource.java index 4f9ef958e676a9f7e743d6820b7df7c41120f7db..4159d7a60419eb08d1b6d833b8f5242bcb9632fe 100644 --- a/src/main/java/caosdb/server/resource/ScriptingResource.java +++ b/src/main/java/caosdb/server/resource/ScriptingResource.java @@ -213,7 +213,7 @@ public class ScriptingResource extends AbstractCaosDBServerResource { * the call is not configured to be called by everyone, a SessionToken is returned instead. */ public Object generateAuthToken(String call) { - String purpose = "scripting:" + call; + String purpose = ScriptingPermissions.PERMISSION_EXECUTION(call); Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser()); if (authtoken != null || isAnonymous()) { return authtoken; diff --git a/src/test/java/caosdb/server/authentication/AuthTokenTest.java b/src/test/java/caosdb/server/authentication/AuthTokenTest.java index 7fb22fe96fb6d8140a9834d5cc2061d48bb8d637..ea0807f5497dedb5c2d87d5f13a240ba3ef0ed7c 100644 --- a/src/test/java/caosdb/server/authentication/AuthTokenTest.java +++ b/src/test/java/caosdb/server/authentication/AuthTokenTest.java @@ -44,6 +44,9 @@ import caosdb.server.resource.TestScriptingResource.RetrievePasswordValidator; import caosdb.server.resource.TestScriptingResource.RetrievePermissionRules; import caosdb.server.resource.TestScriptingResource.RetrieveRole; import caosdb.server.resource.TestScriptingResource.RetrieveUser; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -262,7 +265,7 @@ public class AuthTokenTest { Assert.assertEquals( Integer.parseInt( CaosDBServer.getServerProperty(ServerProperties.KEY_ONE_TIME_TOKEN_EXPIRES_MS)), - config.getTimeout()); + config.getExpiresAfter()); Assert.assertEquals(1, config.getMaxReplays()); Assert.assertNull("no purpose", config.getPurpose()); @@ -289,7 +292,7 @@ public class AuthTokenTest { Assert.assertNotNull("test purpose there", map.get("test purpose 1")); 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(10000, config.getExpiresAfter()); Assert.assertEquals(3, config.getMaxReplays()); Assert.assertArrayEquals( @@ -408,4 +411,34 @@ public class AuthTokenTest { Subject anonymous = SecurityUtils.getSubject(); anonymous.login(token); } + + @Test + public void testIntInConfigYaml() throws IOException { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("- expiresAfterSeconds: 1000\n"); + testYaml.append(" replayTimeout: 1000\n"); + + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + ObjectReader reader = mapper.readerFor(Config.class); + Config config = + (Config) reader.readValues(new CharSequenceInputStream(testYaml, "utf-8")).next(); + + assertEquals(1000000, config.getExpiresAfter()); + assertEquals(1000, config.getReplayTimeout()); + } + + @Test + public void testLongInConfigYaml() throws IOException { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("- expiresAfter: 9223372036854775000\n"); + testYaml.append(" replayTimeoutSeconds: 922337203685477\n"); + + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + ObjectReader reader = mapper.readerFor(Config.class); + Config config = + (Config) reader.readValues(new CharSequenceInputStream(testYaml, "utf-8")).next(); + + assertEquals(9223372036854775000L, config.getExpiresAfter()); + assertEquals(922337203685477000L, config.getReplayTimeout()); + } }