From d3dceac4d8e4c855711e97b47b155bb3d7b1bf86 Mon Sep 17 00:00:00 2001 From: Timm Fitschen <t.fitschen@indiscale.com> Date: Wed, 6 May 2020 15:12:43 +0200 Subject: [PATCH] WIP: one time token on startup --- pom.xml | 5 + src/main/java/caosdb/server/CaosDBServer.java | 90 ++++----- .../OneTimeAuthenticationToken.java | 178 ++++++++++++++++-- .../accessControl/OneTimeTokenRealm.java | 2 +- .../server/accessControl/SessionToken.java | 2 +- .../server/resource/ScriptingResource.java | 52 ++--- .../scripting/ScriptingPermissions.java | 11 ++ .../scripting/ServerSideScriptingCaller.java | 8 +- .../server/authentication/AuthTokenTest.java | 110 ++++++++++- .../resource/TestScriptingResource.java | 23 ++- .../server/utils/WebinterfaceUtilsTest.java | 7 +- 11 files changed, 397 insertions(+), 91 deletions(-) create mode 100644 src/main/java/caosdb/server/scripting/ScriptingPermissions.java diff --git a/pom.xml b/pom.xml index be9279b6..acd175a3 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,11 @@ <artifactId>easy-units</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-yaml</artifactId> + <version>2.11.0</version> + </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> diff --git a/src/main/java/caosdb/server/CaosDBServer.java b/src/main/java/caosdb/server/CaosDBServer.java index 34bfe3e2..4a6a3ad0 100644 --- a/src/main/java/caosdb/server/CaosDBServer.java +++ b/src/main/java/caosdb/server/CaosDBServer.java @@ -19,10 +19,54 @@ */ package caosdb.server; +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Properties; +import java.util.TimeZone; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.config.Ini; +import org.apache.shiro.config.Ini.Section; +import org.apache.shiro.config.IniSecurityManagerFactory; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.Factory; +import org.apache.shiro.util.ThreadContext; +import org.restlet.Application; +import org.restlet.Component; +import org.restlet.Context; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.Server; +import org.restlet.data.CookieSetting; +import org.restlet.data.Parameter; +import org.restlet.data.Protocol; +import org.restlet.data.Reference; +import org.restlet.data.ServerInfo; +import org.restlet.data.Status; +import org.restlet.engine.Engine; +import org.restlet.routing.Route; +import org.restlet.routing.Router; +import org.restlet.routing.Template; +import org.restlet.routing.TemplateRoute; +import org.restlet.routing.Variable; +import org.restlet.util.Series; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import caosdb.server.accessControl.AnonymousRealm; import caosdb.server.accessControl.AuthenticationUtils; import caosdb.server.accessControl.CaosDBAuthorizingRealm; import caosdb.server.accessControl.CaosDBDefaultRealm; +import caosdb.server.accessControl.OneTimeAuthenticationToken; import caosdb.server.accessControl.OneTimeTokenRealm; import caosdb.server.accessControl.Principal; import caosdb.server.accessControl.SessionToken; @@ -64,49 +108,6 @@ import caosdb.server.utils.FileUtils; import caosdb.server.utils.Initialization; import caosdb.server.utils.NullPrintStream; import caosdb.server.utils.Utils; -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Properties; -import java.util.TimeZone; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogRecord; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.config.Ini; -import org.apache.shiro.config.Ini.Section; -import org.apache.shiro.config.IniSecurityManagerFactory; -import org.apache.shiro.mgt.SecurityManager; -import org.apache.shiro.subject.Subject; -import org.apache.shiro.util.Factory; -import org.apache.shiro.util.ThreadContext; -import org.restlet.Application; -import org.restlet.Component; -import org.restlet.Context; -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.Restlet; -import org.restlet.Server; -import org.restlet.data.CookieSetting; -import org.restlet.data.Parameter; -import org.restlet.data.Protocol; -import org.restlet.data.Reference; -import org.restlet.data.ServerInfo; -import org.restlet.data.Status; -import org.restlet.engine.Engine; -import org.restlet.routing.Route; -import org.restlet.routing.Router; -import org.restlet.routing.Template; -import org.restlet.routing.TemplateRoute; -import org.restlet.routing.Variable; -import org.restlet.util.Series; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class CaosDBServer extends Application { @@ -261,7 +262,8 @@ public class CaosDBServer extends Application { init(args); initServerProperties(); initTimeZone(); - } catch (IOException | InterruptedException e1) { + OneTimeAuthenticationToken.init(); + } catch (Exception e1) { logger.error("Could not configure the server.", e1); System.exit(1); } diff --git a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java index ba7dff8d..ddb93080 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java @@ -22,15 +22,29 @@ */ package caosdb.server.accessControl; -import caosdb.server.CaosDBServer; -import caosdb.server.ServerProperties; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.UUID; +import org.apache.shiro.subject.Subject; import org.eclipse.jetty.util.ajax.JSON; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import caosdb.server.CaosDBServer; +import caosdb.server.ServerProperties; public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToken { private static final transient String PEPPER = java.util.UUID.randomUUID().toString(); private final String[] permissions; + private String[] roles; public OneTimeAuthenticationToken( final Principal principal, @@ -39,9 +53,11 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final String salt, final String curry, final String checksum, - final String... permissions) { + final String[] permissions, + final String[] roles) { super(principal, date, timeout, salt, curry, checksum); this.permissions = permissions; + this.roles = roles; } public OneTimeAuthenticationToken( @@ -50,8 +66,9 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final long timeout, final String salt, final String curry, - final String... permissions) { - super( + final String[] permissions, + final String[] roles) { + this( principal, date, timeout, @@ -65,8 +82,10 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke salt, curry, calcChecksum((Object[]) permissions), - PEPPER)); - this.permissions = permissions; + calcChecksum((Object[]) roles), + PEPPER), + permissions, + roles); } private static final long serialVersionUID = -1072740888045267613L; @@ -75,6 +94,10 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke return this.permissions; } + public String[] getRoles() { + return this.roles; + } + @Override public String calcChecksum() { return calcChecksum( @@ -85,6 +108,7 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke this.salt, this.curry, calcChecksum((Object[]) this.permissions), + calcChecksum((Object[]) this.roles), PEPPER); } @@ -98,6 +122,7 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke this.timeout, this.salt, this.permissions, + this.roles, this.checksum }); } @@ -117,19 +142,148 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final long timeout = (Long) array[3]; final String salt = (String) array[4]; final String[] permissions = toStringArray((Object[]) array[5]); - final String checksum = (String) array[6]; + final String[] roles = toStringArray((Object[]) array[6]); + final String checksum = (String) array[7]; return new OneTimeAuthenticationToken( - principal, date, timeout, salt, curry, checksum, permissions); + principal, date, timeout, salt, curry, checksum, permissions, roles); } public static OneTimeAuthenticationToken generate( - final Principal principal, final String curry, final String... permissions) { + final Principal principal, final String curry, final String[] permissions, String[] roles) { return new OneTimeAuthenticationToken( principal, System.currentTimeMillis(), Long.parseLong(CaosDBServer.getServerProperty(ServerProperties.KEY_ACTIVATION_TIMEOUT_MS)), UUID.randomUUID().toString(), curry, - permissions); + permissions, + roles); + } + + + public static List<Config> loadConfig(InputStream input) throws Exception { + List<Config> results = new LinkedList<>(); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + ObjectReader reader = mapper.readerFor(Config.class); + Iterator<Config> configs = reader.readValues(input); + configs.forEachRemaining(results::add); + return results; + } + + public static Map<String, Config> getPurposeMap(List<Config> configs) throws Exception { + Map<String, Config> result = new HashMap<>(); + for(Config config : configs) { + if(config.getPurpose() != null && !config.getPurpose().isEmpty()) { + if(result.containsKey(config.getPurpose())) { + throw new Exception("OneTimeAuthToken configuration contains duplicate values for the 'purpose' property."); + } + result.put(config.getPurpose(), config); + } + } + return result; + } + + public static OneTimeAuthenticationToken generate(Config c) { + return generate(c, new Principal("anonymous", "anonymous"), null); + } + + public static OneTimeAuthenticationToken generateForPurpose(String purpose, Subject user, String curry) { + Config c = purposes.get(purpose); + if (c != null) { + Principal principal = (Principal) user.getPrincipal(); + return generate(c, principal, curry); + } + return null; + } + + public static OneTimeAuthenticationToken generate(Config c, Principal principal, String curry) { + return generate(principal, curry, c.getPermissions(), c.getRoles()); + } + + static Map<String, Config> purposes; + + public static class Output { + private String file = null; + + public Output() { + } + + public void output(OneTimeAuthenticationToken t) throws IOException { + try (PrintWriter writer = new PrintWriter(file, "utf-8")) { + writer.println(t.toString()); + } + } + + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } } -} + + public static class Config { + private String[] permissions = {}; + private String[] roles = {}; + private String purpose = null; + private Output output = null; + + public Config() { + } + + public String[] getPermissions() { + return permissions; + } + public String getPurpose() { + return purpose; + } + public void setPermissions(String[] permissions) { + this.permissions = permissions; + } + public String[] getRoles() { + return roles; + } + public void setRoles(String[] roles) { + this.roles = roles; + } + public void setPurpose(String purpose) { + this.purpose = purpose; + } + + public Output getOutput() { + return output; + } + + public void setOutput(Output output) { + this.output = output; + } + + } + + public static Map<String, Config> getPurposeMap() { + return purposes; + } + + + public static void init(InputStream yamlConfig) throws Exception { + List<Config> configs = loadConfig(yamlConfig); + output(configs); + purposes = getPurposeMap(configs); + } + + private static void output(List<Config> configs) throws IOException { + for (Config config : configs) { + if(config.getOutput() != null) { + config.getOutput().output(generate(config)); + } + } + } + + public static void init() throws Exception { + try(FileInputStream f = new FileInputStream("conf/ext/authtoken.yaml")) { + init(f); + } + } + +} \ No newline at end of file diff --git a/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java b/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java index 468a8d8d..1c16bf0c 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java @@ -22,11 +22,11 @@ */ package caosdb.server.accessControl; -import caosdb.server.accessControl.CaosDBAuthorizingRealm.PermissionAuthenticationInfo; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; +import caosdb.server.accessControl.CaosDBAuthorizingRealm.PermissionAuthenticationInfo; public class OneTimeTokenRealm extends SessionTokenRealm { diff --git a/src/main/java/caosdb/server/accessControl/SessionToken.java b/src/main/java/caosdb/server/accessControl/SessionToken.java index 273bd50b..82b11f22 100644 --- a/src/main/java/caosdb/server/accessControl/SessionToken.java +++ b/src/main/java/caosdb/server/accessControl/SessionToken.java @@ -22,9 +22,9 @@ */ package caosdb.server.accessControl; +import org.eclipse.jetty.util.ajax.JSON; import caosdb.server.CaosDBServer; import caosdb.server.ServerProperties; -import org.eclipse.jetty.util.ajax.JSON; public class SessionToken extends SelfValidatingAuthenticationToken { diff --git a/src/main/java/caosdb/server/resource/ScriptingResource.java b/src/main/java/caosdb/server/resource/ScriptingResource.java index b23a9593..d967d031 100644 --- a/src/main/java/caosdb/server/resource/ScriptingResource.java +++ b/src/main/java/caosdb/server/resource/ScriptingResource.java @@ -24,18 +24,6 @@ package caosdb.server.resource; -import caosdb.server.FileSystem; -import caosdb.server.accessControl.Principal; -import caosdb.server.accessControl.SessionToken; -import caosdb.server.accessControl.UserSources; -import caosdb.server.entity.FileProperties; -import caosdb.server.entity.Message; -import caosdb.server.scripting.CallerSerializer; -import caosdb.server.scripting.ServerSideScriptingCaller; -import caosdb.server.utils.Serializer; -import caosdb.server.utils.ServerMessages; -import caosdb.server.utils.Utils; -import com.ibm.icu.text.Collator; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -48,6 +36,7 @@ import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.shiro.subject.Subject; import org.jdom2.Element; import org.restlet.data.CharacterSet; import org.restlet.data.Form; @@ -57,6 +46,20 @@ import org.restlet.data.Status; import org.restlet.engine.header.ContentType; import org.restlet.ext.fileupload.RestletFileUpload; import org.restlet.representation.Representation; +import com.ibm.icu.text.Collator; +import caosdb.server.FileSystem; +import caosdb.server.accessControl.OneTimeAuthenticationToken; +import caosdb.server.accessControl.Principal; +import caosdb.server.accessControl.SessionToken; +import caosdb.server.accessControl.UserSources; +import caosdb.server.entity.FileProperties; +import caosdb.server.entity.Message; +import caosdb.server.scripting.CallerSerializer; +import caosdb.server.scripting.ScriptingPermissions; +import caosdb.server.scripting.ServerSideScriptingCaller; +import caosdb.server.utils.Serializer; +import caosdb.server.utils.ServerMessages; +import caosdb.server.utils.Utils; public class ScriptingResource extends AbstractCaosDBServerResource { @@ -83,9 +86,6 @@ public class ScriptingResource extends AbstractCaosDBServerResource { @Override protected Representation httpPostInChildClass(Representation entity) throws Exception { - if (isAnonymous()) { - return error(ServerMessages.AUTHORIZATION_ERROR, Status.CLIENT_ERROR_FORBIDDEN); - } MediaType mediaType = entity.getMediaType(); try { if (mediaType.equals(MediaType.MULTIPART_FORM_DATA, true)) { @@ -193,28 +193,38 @@ public class ScriptingResource extends AbstractCaosDBServerResource { public int callScript(Form form, List<FileProperties> files) throws Message { List<String> commandLine = form2CommandLine(form); + String call = commandLine.get(0); + + checkExecutionPermission(getUser(), call); Integer timeoutMs = Integer.valueOf(form.getFirstValue("timeout", "-1")); return callScript(commandLine, timeoutMs, files); } public int callScript(List<String> commandLine, Integer timeoutMs, List<FileProperties> files) throws Message { - return callScript(commandLine, timeoutMs, files, generateAuthToken()); + return callScript(commandLine, timeoutMs, files, generateAuthToken(commandLine.get(0))); } - public Object generateAuthToken() { + public boolean isAnonymous() { + return getUser().hasRole(UserSources.ANONYMOUS_ROLE); + } + + public Object generateAuthToken(String call) { + String purpose = "scripting:" + call; + Object authtoken = OneTimeAuthenticationToken.generateForPurpose(purpose, getUser(), null); + if (authtoken != null || isAnonymous()) { + return authtoken; + } return SessionToken.generate((Principal) getUser().getPrincipal(), null); } - boolean isAnonymous() { - boolean ret = getUser().hasRole(UserSources.ANONYMOUS_ROLE); - return ret; + public void checkExecutionPermission(Subject user, String call) { + user.checkPermission(ScriptingPermissions.PERMISSION_EXECUTION(call)); } public int callScript( List<String> commandLine, Integer timeoutMs, List<FileProperties> files, Object authToken) throws Message { - // TODO getUser().checkPermission("SCRIPTING:EXECUTE:" + commandLine.get(0)); caller = new ServerSideScriptingCaller( commandLine.toArray(new String[commandLine.size()]), timeoutMs, files, authToken); diff --git a/src/main/java/caosdb/server/scripting/ScriptingPermissions.java b/src/main/java/caosdb/server/scripting/ScriptingPermissions.java new file mode 100644 index 00000000..b1417035 --- /dev/null +++ b/src/main/java/caosdb/server/scripting/ScriptingPermissions.java @@ -0,0 +1,11 @@ +package caosdb.server.scripting; + +public class ScriptingPermissions { + + public static final String PERMISSION_EXECUTION(final String call) { + StringBuilder ret = new StringBuilder(10 + call.length()); + ret.append("SCRIPTING:EXECUTE:"); + ret.append(call.replace("/", ":")); + return ret.toString(); + } +} diff --git a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java index 6fa5d1a4..fa51d5ff 100644 --- a/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java +++ b/src/main/java/caosdb/server/scripting/ServerSideScriptingCaller.java @@ -35,6 +35,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.ProcessBuilder.Redirect; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -255,7 +256,12 @@ public class ServerSideScriptingCaller { } int callScript() throws IOException, InterruptedException, TimeoutException { - String[] effectiveCommandLine = injectAuthToken(getCommandLine()); + String[] effectiveCommandLine; + if (authToken != null) { + effectiveCommandLine = injectAuthToken(getCommandLine()); + } else { + effectiveCommandLine = Arrays.copyOf(getCommandLine(), getCommandLine().length); + } effectiveCommandLine[0] = makeCallAbsolute(effectiveCommandLine[0]); final ProcessBuilder pb = new ProcessBuilder(effectiveCommandLine); diff --git a/src/test/java/caosdb/server/authentication/AuthTokenTest.java b/src/test/java/caosdb/server/authentication/AuthTokenTest.java index 9937dc60..7337d6e3 100644 --- a/src/test/java/caosdb/server/authentication/AuthTokenTest.java +++ b/src/test/java/caosdb/server/authentication/AuthTokenTest.java @@ -22,15 +22,23 @@ */ package caosdb.server.authentication; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Map; +import org.apache.commons.io.input.CharSequenceInputStream; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; import caosdb.server.CaosDBServer; import caosdb.server.accessControl.AuthenticationUtils; import caosdb.server.accessControl.OneTimeAuthenticationToken; +import caosdb.server.accessControl.OneTimeAuthenticationToken.Config; import caosdb.server.accessControl.Principal; import caosdb.server.accessControl.SessionToken; -import java.io.IOException; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; public class AuthTokenTest { @@ -192,7 +200,7 @@ public class AuthTokenTest { } @Test - public void testOneTimeToken() { + public void testOneTimeTokenSerialization() { final String curry = null; final OneTimeAuthenticationToken t1 = new OneTimeAuthenticationToken( @@ -201,7 +209,8 @@ public class AuthTokenTest { 60000L, "sdfh37456sd", curry, - new String[] {""}); + new String[] {"permissions"}, + new String[] {"roles"}); System.err.println(t1.toString()); Assert.assertFalse(t1.isExpired()); Assert.assertTrue(t1.isHashValid()); @@ -213,4 +222,93 @@ public class AuthTokenTest { Assert.assertTrue(OneTimeAuthenticationToken.parse(t1.toString(), curry).isHashValid()); Assert.assertTrue(OneTimeAuthenticationToken.parse(t1.toString(), curry).isValid()); } + + @Test + public void testOneTimeTokenConfigBasic() throws Exception { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("purpose: test purpose 1\n"); + testYaml.append("roles: [ role1, \"role2\"]\n"); + testYaml.append("permissions:\n"); + testYaml.append(" - permission1\n"); + testYaml.append(" - 'permission2'\n"); + testYaml.append(" - \"permission3\"\n"); + testYaml.append(" - \"permission with white space\"\n"); + + OneTimeAuthenticationToken.init(new CharSequenceInputStream(testYaml, "utf-8")); + Map<String, Config> map = OneTimeAuthenticationToken.getPurposeMap(); + 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.assertArrayEquals("permissions parsed", new String[] {"permission1", "permission2", "permission3", "permission with white space"}, config.getPermissions()); + Assert.assertArrayEquals("roles parsed", new String[] {"role1", "role2" }, config.getRoles()); + } + + @Test + public void testOneTimeTokenConfigNoRoles() throws Exception { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("purpose: no roles test\n"); + testYaml.append("permissions:\n"); + testYaml.append(" - permission1\n"); + testYaml.append(" - 'permission2'\n"); + testYaml.append(" - \"permission3\"\n"); + testYaml.append(" - \"permission with white space\"\n"); + + OneTimeAuthenticationToken.init(new CharSequenceInputStream(testYaml, "utf-8")); + Map<String, Config> map = OneTimeAuthenticationToken.getPurposeMap(); + Config config = map.get("no roles test"); + + Assert.assertArrayEquals("empty roles array parsed", new String[] {}, config.getRoles()); + } + + @Test + public void testOneTimeTokenConfigNoPurpose() throws Exception { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("permissions:\n"); + testYaml.append(" - permission1\n"); + testYaml.append(" - 'permission2'\n"); + testYaml.append(" - \"permission3\"\n"); + testYaml.append(" - \"permission with white space\"\n"); + + OneTimeAuthenticationToken.init(new CharSequenceInputStream(testYaml, "utf-8")); + Map<String, Config> map = OneTimeAuthenticationToken.getPurposeMap(); + Assert.assertEquals(map.size(), 0); + } + + @Test + public void testOneTimeTokenConfigMulti() throws Exception { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("- purpose: purpose 1\n"); + testYaml.append("- purpose: purpose 2\n"); + testYaml.append("- purpose: purpose 3\n"); + testYaml.append("- purpose: purpose 4\n"); + + OneTimeAuthenticationToken.init(new CharSequenceInputStream(testYaml, "utf-8")); + Map<String, Config> map = OneTimeAuthenticationToken.getPurposeMap(); + Assert.assertEquals("four items", 4, map.size()); + Assert.assertTrue(map.get("purpose 2") instanceof Config); + } + + @Test + public void testOneTimeTokenConfigOutputFile() throws Exception { + File tempFile = File.createTempFile("authtoken", "json"); + tempFile.deleteOnExit(); + + StringBuilder testYaml = new StringBuilder(); + testYaml.append("- output:\n"); + testYaml.append(" file: " + tempFile.getAbsolutePath() + "\n"); + testYaml.append(" permissions: [ permission1 ]\n"); + + // write the token + OneTimeAuthenticationToken.init(new CharSequenceInputStream(testYaml, "utf-8")); + Assert.assertTrue(tempFile.exists()); + try ( BufferedReader reader = new BufferedReader(new FileReader(tempFile))) { + OneTimeAuthenticationToken token = OneTimeAuthenticationToken.parse(reader.readLine(), null); + assertEquals("Token has anonymous username", "anonymous", token.getPrincipal().getUsername()); + assertEquals("Token has anonymous realm", "anonymous", token.getPrincipal().getRealm()); + assertArrayEquals("Permissions array has been written and read", new String[] { "permission1" }, token.getPermissions()); + } + + } + } diff --git a/src/test/java/caosdb/server/resource/TestScriptingResource.java b/src/test/java/caosdb/server/resource/TestScriptingResource.java index 2edfb82b..e1538573 100644 --- a/src/test/java/caosdb/server/resource/TestScriptingResource.java +++ b/src/test/java/caosdb/server/resource/TestScriptingResource.java @@ -23,7 +23,6 @@ package caosdb.server.resource; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import caosdb.server.CaosDBServer; import caosdb.server.accessControl.AuthenticationUtils; @@ -177,7 +176,7 @@ public class TestScriptingResource { }; @Override - public Object generateAuthToken() { + public Object generateAuthToken(String purpose) { return ""; } }; @@ -201,10 +200,26 @@ public class TestScriptingResource { } @Test - public void testAnonymous() { + public void testAnonymousWithOutPermission() { + Subject user = SecurityUtils.getSubject(); + user.login(AuthenticationUtils.ANONYMOUS_USER); + Representation entity = new StringRepresentation("asdf"); + entity.setMediaType(MediaType.TEXT_ALL); + Request request = new Request(Method.POST, "../test", entity); + request.setRootRef(new Reference("bla")); + request.getAttributes().put("SRID", "asdf1234"); + request.setDate(new Date()); + request.setHostRef("bla"); + resource.init(null, request, new Response(null)); + + resource.handle(); + assertEquals(Status.CLIENT_ERROR_FORBIDDEN, resource.getResponse().getStatus()); + } + + @Test + public void testAnonymousWithPermission() { Subject user = SecurityUtils.getSubject(); user.login(AuthenticationUtils.ANONYMOUS_USER); - assertTrue(resource.isAnonymous()); Representation entity = new StringRepresentation("asdf"); entity.setMediaType(MediaType.TEXT_ALL); Request request = new Request(Method.POST, "../test", entity); diff --git a/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java b/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java index 4ff5d718..22dc38c7 100644 --- a/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java +++ b/src/test/java/caosdb/server/utils/WebinterfaceUtilsTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import caosdb.server.CaosDBServer; +import caosdb.server.ServerProperties; import java.io.IOException; import org.junit.BeforeClass; import org.junit.Rule; @@ -26,7 +27,11 @@ public class WebinterfaceUtilsTest { WebinterfaceUtils utils = new WebinterfaceUtils(new Reference("https://host:2345/some_path")); String buildNumber = utils.getBuildNumber(); String ref = utils.getWebinterfaceURI("sub"); - assertEquals("https://host:2345/webinterface/" + buildNumber + "/sub", ref); + String contextRoot = CaosDBServer.getServerProperty(ServerProperties.KEY_CONTEXT_ROOT); + contextRoot = + contextRoot != null ? "/" + contextRoot.replaceFirst("^/", "").replaceFirst("/$", "") : ""; + + assertEquals("https://host:2345" + contextRoot + "/webinterface/" + buildNumber + "/sub", ref); } @Test -- GitLab