diff --git a/pom.xml b/pom.xml index acd175a3fdcf4f84add6f0d94c96c5f1944fef6e..8c41bd2b67df7a812a19772297be0cd4f7819da4 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,11 @@ <artifactId>easy-units</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> + <dependency> + <groupId>org.quartz-scheduler</groupId> + <artifactId>quartz</artifactId> + <version>2.3.2</version> + </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> diff --git a/scripting/bin/administration/diagnostics.py b/scripting/bin/administration/diagnostics.py new file mode 100755 index 0000000000000000000000000000000000000000..3c1f2eed66b564fb6d73e320bd023e99f32632c6 --- /dev/null +++ b/scripting/bin/administration/diagnostics.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2018 Research Group Biomedical Physics, +# Max-Planck-Institute for Dynamics and Self-Organization Göttingen +# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com> +# Copyright (C) 2020 IndiScale GmbH <info@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 +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# ** end header +# +"""diagnostics.py + +A script which returns a json representation of various parameters which might +be interesting for debugging the server-side scripting functionality and which +should not be executable for non-admin users. +""" + +import sys + +TEST_MODULES = [ + "caosdb", + "numpy", + "pandas", +] + + +def get_option(name, default=None): + for arg in sys.argv: + if arg.startswith("--{}=".format(name)): + index = len(name) + 3 + return arg[index:] + return default + +def get_exit_code(): + return int(get_option("exit", 0)) + +def get_auth_token(): + return get_option("auth-token") + +def get_query(): + return get_option("query") + + +def get_caosdb_info(auth_token): + import caosdb as db + result = dict() + + try: + db.configure_connection(auth_token=auth_token, password_method="auth_token") + + info = db.Info() + + result["info"] = str(info) + result["username"] = info.user_info.name + result["realm"] = info.user_info.realm + result["roles"] = info.user_info.roles + + # execute a query and return the results + query = get_query() + if query is not None: + query_result = db.execute_query(query) + result["query"] = (query, str(query_result)) + + except Exception as e: + result["exception"] = str(e) + return result + +def test_imports(modules): + result = dict() + for m in modules: + try: + i = __import__(m) + v = i.__version__ if hasattr(i, "__version__") and i.__version__ is not None else "unknown version" + result[m] = (True, v) + except ImportError as e: + result[m] = (False, str(e)) + return result + +def main(): + try: + import json + except ImportError: + print('{"python_version":"{v}",' + '"python_path":["{p}"]}'.format(v=sys.version, + p='","'.join(sys.path))) + raise + + try: + diagnostics = dict() + diagnostics["python_version"] = sys.version + diagnostics["python_path"] = sys.path + diagnostics["call"] = sys.argv + diagnostics["import"] = test_imports(TEST_MODULES) + + auth_token = get_auth_token() + diagnostics["auth_token"] = auth_token + + if diagnostics["import"]["caosdb"][0] is True: + diagnostics["caosdb"] = get_caosdb_info(auth_token) + + finally: + json.dump(diagnostics, sys.stdout) + + sys.exit(get_exit_code()) + + +if __name__ == "__main__": + main() diff --git a/src/main/java/caosdb/server/CaosAuthenticator.java b/src/main/java/caosdb/server/CaosAuthenticator.java index 9272b41ecf45158440da841415882ef5d7969d31..021f4ab7d4541756df63fa4bf6878fa603179679 100644 --- a/src/main/java/caosdb/server/CaosAuthenticator.java +++ b/src/main/java/caosdb/server/CaosAuthenticator.java @@ -24,6 +24,7 @@ package caosdb.server; import caosdb.server.accessControl.AuthenticationUtils; import caosdb.server.resource.DefaultResource; +import caosdb.server.utils.ServerMessages; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; @@ -74,7 +75,7 @@ public class CaosAuthenticator extends Authenticator { @Override protected int unauthenticated(final Request request, final Response response) { final DefaultResource defaultResource = - new DefaultResource(AuthenticationUtils.UNAUTHENTICATED.toElement()); + new DefaultResource(ServerMessages.UNAUTHENTICATED.toElement()); defaultResource.init(getContext(), request, response); defaultResource.handle(); response.setStatus(org.restlet.data.Status.CLIENT_ERROR_UNAUTHORIZED); diff --git a/src/main/java/caosdb/server/CaosDBServer.java b/src/main/java/caosdb/server/CaosDBServer.java index ca1e64feec699407b4e35f642522f599440184fd..be705aa58eef7ba045391322d24e267524ebee05 100644 --- a/src/main/java/caosdb/server/CaosDBServer.java +++ b/src/main/java/caosdb/server/CaosDBServer.java @@ -84,6 +84,11 @@ 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.quartz.JobDetail; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.impl.StdSchedulerFactory; import org.restlet.Application; import org.restlet.Component; import org.restlet.Context; @@ -257,6 +262,7 @@ public class CaosDBServer extends Application { throws SecurityException, FileNotFoundException, IOException { try { init(args); + initScheduler(); initServerProperties(); initTimeZone(); OneTimeAuthenticationToken.init(); @@ -357,6 +363,11 @@ public class CaosDBServer extends Application { } } + private static void initScheduler() throws SchedulerException { + SCHEDULER = StdSchedulerFactory.getDefaultScheduler(); + SCHEDULER.start(); + } + private static void initDatatypes(final Access access) throws Exception { final RetrieveDatatypes t = new RetrieveDatatypes(); t.setAccess(access); @@ -509,6 +520,7 @@ 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. @@ -821,6 +833,10 @@ public class CaosDBServer extends Application { public static Properties getServerProperties() { return SERVER_PROPERTIES; } + + public static void scheduleJob(JobDetail job, Trigger trigger) throws SchedulerException { + SCHEDULER.scheduleJob(job, trigger); + } } class CaosDBComponent extends Component { diff --git a/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java b/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java index 4a52c50196cd61242fd15e94409e8581eabda12a..651aeab6758303bbc0f0419878eaa7a421dbaa21 100644 --- a/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java +++ b/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java @@ -26,8 +26,6 @@ import static caosdb.server.utils.Utils.URLDecodeWithUTF8; import caosdb.server.CaosDBServer; import caosdb.server.ServerProperties; -import caosdb.server.entity.Message; -import caosdb.server.entity.Message.MessageType; import caosdb.server.permissions.ResponsibleAgent; import caosdb.server.permissions.Role; import caosdb.server.utils.Utils; @@ -52,8 +50,6 @@ public class AuthenticationUtils { private static final Logger logger = LoggerFactory.getLogger(AuthenticationUtils.class); public static final String ONE_TIME_TOKEN_COOKIE = "OneTimeToken"; - public static final Message UNAUTHENTICATED = - new Message(MessageType.Error, 401, "Sign up, please!"); public static final String SESSION_TOKEN_COOKIE = "SessionToken"; public static final String SESSION_TIMEOUT_COOKIE = "SessionTimeOut"; diff --git a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java index 67e7be5e3171d4d967f7c83335e0512d8a8ac92e..69ab0b546294d9753ee809dfdccafc0c6d708a12 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java @@ -36,11 +36,58 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.subject.Subject; import org.eclipse.jetty.util.ajax.JSON; +import org.quartz.CronScheduleBuilder; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDataMap; +import org.quartz.JobDetail; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.quartz.SchedulerException; +import org.quartz.Trigger; +import org.quartz.TriggerBuilder; + +class ConsumedInfo { + + private OneTimeAuthenticationToken oneTimeAuthenticationToken; + private List<Long> attempts = new LinkedList<>(); + + public ConsumedInfo(OneTimeAuthenticationToken oneTimeAuthenticationToken) { + this.oneTimeAuthenticationToken = oneTimeAuthenticationToken; + } + + public String getKey() { + return getKey(oneTimeAuthenticationToken); + } + + public static String getKey(OneTimeAuthenticationToken token) { + return token.checksum; + } + + public int getNoOfAttempts() { + return attempts.size(); + } + + public long getMaxAttempts() { + return oneTimeAuthenticationToken.getMaxAttempts(); + } + + public void consume() { + if (getNoOfAttempts() >= getMaxAttempts()) { + throw new AuthenticationException("One-token was consumed too often."); + } + attempts.add(System.currentTimeMillis()); + } +} public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToken { + private static Map<String, ConsumedInfo> consumedOneTimeTokens = new HashMap<>(); + private long maxAttempts; + public OneTimeAuthenticationToken( final Principal principal, final long date, @@ -49,8 +96,29 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final String curry, final String checksum, final String[] permissions, - final String[] roles) { + final String[] roles, + final long maxAttempts) { super(principal, date, timeout, salt, curry, checksum, permissions, roles); + this.maxAttempts = maxAttempts; + consume(); + } + + public void consume() { + consume(this); + } + + public static void consume(OneTimeAuthenticationToken oneTimeAuthenticationToken) { + if (oneTimeAuthenticationToken.isValid()) { + String key = ConsumedInfo.getKey(oneTimeAuthenticationToken); + ConsumedInfo consumedInfo = consumedOneTimeTokens.get(key); + if (consumedInfo == null) { + consumedInfo = new ConsumedInfo(oneTimeAuthenticationToken); + consumedOneTimeTokens.put(key, consumedInfo); + } + consumedInfo.consume(); + } else { + oneTimeAuthenticationToken.isValid(); + } } public OneTimeAuthenticationToken( @@ -58,12 +126,20 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final long timeout, final String curry, final String[] permissions, - final String[] roles) { - super(principal, timeout, curry, permissions, roles); + final String[] roles, + final Long maxAttempts) { + super(principal, timeout, curry, permissions, roles, maxAttempts != null ? maxAttempts : 1); } private static final long serialVersionUID = -1072740888045267613L; + /** + * Return consumed. + * + * @param array + * @param curry + * @return + */ public static OneTimeAuthenticationToken parse(final Object[] array, final String curry) { final Principal principal = new Principal((String) array[1], (String) array[2]); final String[] roles = toStringArray((Object[]) array[3]); @@ -72,16 +148,21 @@ 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]; return new OneTimeAuthenticationToken( - principal, date, timeout, salt, curry, checksum, permissions, roles); + principal, date, timeout, salt, curry, checksum, permissions, roles, maxAttempts); } private static OneTimeAuthenticationToken generate( - final Principal principal, final String curry, final String[] permissions, String[] roles) { - int timeout = - Integer.parseInt( - CaosDBServer.getServerProperty(ServerProperties.KEY_ACTIVATION_TIMEOUT_MS)); - return new OneTimeAuthenticationToken(principal, timeout, curry, permissions, roles); + final Principal principal, + final String curry, + final String[] permissions, + final String[] roles, + final long timeout, + final long maxAttempts) { + + return new OneTimeAuthenticationToken( + principal, timeout, curry, permissions, roles, maxAttempts); } public static List<Config> loadConfig(InputStream input) throws Exception { @@ -122,17 +203,19 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke } public static OneTimeAuthenticationToken generate(Config c, Principal principal, String curry) { - return generate(principal, curry, c.getPermissions(), c.getRoles()); + return generate( + principal, curry, c.getPermissions(), c.getRoles(), c.getTimeout(), c.getMaxAttempts()); } static Map<String, Config> purposes; - public static class Output { + public static class Output implements Job { private String file = null; + private String schedule = null; public Output() {} - public void output(OneTimeAuthenticationToken t) throws IOException { + public static void output(OneTimeAuthenticationToken t, String file) throws IOException { try (PrintWriter writer = new PrintWriter(file, "utf-8")) { writer.println(t.toString()); } @@ -145,6 +228,45 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke public void setFile(String file) { this.file = file; } + + public String getSchedule() { + return schedule; + } + + public void setSchedule(String schedule) { + this.schedule = schedule; + } + + public void init(Config config) throws IOException, SchedulerException { + + if (this.schedule != null) { + generate(config); // test config + JobDataMap map = new JobDataMap(); + map.put("config", config); + map.put("file", file); + JobDetail outputJob = JobBuilder.newJob(Output.class).setJobData(map).build(); + Trigger trigger = + TriggerBuilder.newTrigger() + .withIdentity(config.toString()) + .withSchedule(CronScheduleBuilder.cronSchedule(this.schedule)) + .build(); + CaosDBServer.scheduleJob(outputJob, trigger); + } else { + output(generate(config), file); + } + } + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + Config config = (Config) context.getMergedJobDataMap().get("config"); + String file = context.getMergedJobDataMap().getString("file"); + try { + output(generate(config), file); + } catch (IOException e) { + // TODO log + e.printStackTrace(); + } + } } public static class Config { @@ -152,9 +274,33 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke private String[] roles = {}; private String purpose = null; private Output output = null; + private int maxAttempts = 1; + private int timeout = + Integer.parseInt( + CaosDBServer.getServerProperty(ServerProperties.KEY_ACTIVATION_TIMEOUT_MS)); public Config() {} + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public void setExpiresAfterSeconds(int seconds) { + this.setTimeout(seconds * 1000); + } + + public void setMaxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + public int getMaxAttempts() { + return maxAttempts; + } + public String[] getPermissions() { return permissions; } @@ -194,14 +340,14 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke public static void init(InputStream yamlConfig) throws Exception { List<Config> configs = loadConfig(yamlConfig); - output(configs); + initOutput(configs); purposes = getPurposeMap(configs); } - private static void output(List<Config> configs) throws IOException { + private static void initOutput(List<Config> configs) throws IOException, SchedulerException { for (Config config : configs) { if (config.getOutput() != null) { - config.getOutput().output(generate(config)); + config.getOutput().init(config); } } } @@ -212,6 +358,13 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke } } + @Override + protected void setFields(Object[] fields) { + if (fields.length == 1) { + this.maxAttempts = (long) fields[0]; + } + } + @Override public String calcChecksum(String pepper) { return calcChecksum( @@ -224,6 +377,7 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke this.curry, calcChecksum((Object[]) this.permissions), calcChecksum((Object[]) this.roles), + this.maxAttempts, pepper); } @@ -239,7 +393,12 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke this.date, this.timeout, this.salt, - this.checksum + this.checksum, + this.maxAttempts }); } + + public long getMaxAttempts() { + return maxAttempts; + } } diff --git a/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java index 7d4cd846b5226e61856703c137310fd765643a4b..31192c66d38027a870142b32d58e52800af1fb31 100644 --- a/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java @@ -74,7 +74,8 @@ public abstract class SelfValidatingAuthenticationToken extends Principal final String[] permissions, final String[] roles, String checksum, - boolean calcChecksum) { + boolean calcChecksum, + Object... fields) { super(principal); this.date = date; this.timeout = timeout; @@ -82,15 +83,19 @@ public abstract class SelfValidatingAuthenticationToken extends Principal this.curry = curry; this.permissions = permissions != null ? permissions : new String[] {}; this.roles = roles != null ? roles : new String[] {}; + setFields(fields); this.checksum = checksum == null && calcChecksum ? calcChecksum() : checksum; } + protected abstract void setFields(Object[] fields); + public SelfValidatingAuthenticationToken( final Principal principal, final long timeout, final String curry, final String[] permissions, - final String[] roles) { + final String[] roles, + Object... fields) { this( principal, System.currentTimeMillis(), @@ -100,10 +105,11 @@ public abstract class SelfValidatingAuthenticationToken extends Principal permissions, roles, null, - true); + true, + fields); } - public String calcChecksum() { + public final String calcChecksum() { return calcChecksum(PEPPER); } diff --git a/src/main/java/caosdb/server/accessControl/SessionToken.java b/src/main/java/caosdb/server/accessControl/SessionToken.java index 695e4639c065a2b0ee2d31f05e3dce36cf3a54d3..5f6753afba662dc8db1b1aab539a78ff36585fff 100644 --- a/src/main/java/caosdb/server/accessControl/SessionToken.java +++ b/src/main/java/caosdb/server/accessControl/SessionToken.java @@ -86,6 +86,9 @@ public class SessionToken extends SelfValidatingAuthenticationToken { return generate((Principal) subject.getPrincipal(), curry, permissions, roles); } + @Override + protected void setFields(Object[] fields) {} + @Override public String calcChecksum(String pepper) { return calcChecksum( diff --git a/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java b/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java index ff7e59b6ea4575a3ad95264c4f3dfbbcde41b78b..cce2850d6d532ae8880d208682b5677dc0369089 100644 --- a/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java +++ b/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java @@ -43,7 +43,7 @@ public class SessionTokenRealm extends AuthenticatingRealm { } public SessionTokenRealm() { - setAuthenticationTokenClass(SessionToken.class); + setAuthenticationTokenClass(SelfValidatingAuthenticationToken.class); setCredentialsMatcher(new AllowAllCredentialsMatcher()); setCachingEnabled(false); setAuthenticationCachingEnabled(false); diff --git a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java index 013501a0f39b24638c27a162e24015c168f13f3e..1d87c4608e7be3ace4aca09eff4d5e0621cc6b9f 100644 --- a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java +++ b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java @@ -209,13 +209,11 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { if (user != null && user.isAuthenticated()) { Element userInfo = new Element("UserInfo"); - if (!user.getPrincipal().equals(AuthenticationUtils.ANONYMOUS_USER.getPrincipal())) { - // TODO: deprecated - addNameAndRealm(retRoot, user); + // TODO: deprecated, needs refactoring in the webui first + addNameAndRealm(retRoot, user); - // this is the new, correct way - addNameAndRealm(userInfo, user); - } + // this is the new, correct way + addNameAndRealm(userInfo, user); addRoles(userInfo, user); retRoot.addContent(userInfo); @@ -408,11 +406,9 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { getRequest().getAttributes().put("THROWN", t); throw t; } catch (final AuthenticationException e) { - getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN); - return null; + return error(ServerMessages.UNAUTHENTICATED, Status.CLIENT_ERROR_UNAUTHORIZED); } catch (final AuthorizationException e) { - getResponse().setStatus(Status.CLIENT_ERROR_FORBIDDEN); - return null; + return error(ServerMessages.AUTHORIZATION_ERROR, Status.CLIENT_ERROR_FORBIDDEN); } catch (final Message m) { return error(m, Status.CLIENT_ERROR_BAD_REQUEST); } catch (final FileUploadException e) { diff --git a/src/main/java/caosdb/server/resource/FileSystemResource.java b/src/main/java/caosdb/server/resource/FileSystemResource.java index ecaa97fe93cb59fe6a4c58fb60d99a5dca373080..ef352e19eb178c5cefbdbd70d2e48b3231ac7bd7 100644 --- a/src/main/java/caosdb/server/resource/FileSystemResource.java +++ b/src/main/java/caosdb/server/resource/FileSystemResource.java @@ -135,8 +135,9 @@ public class FileSystemResource extends AbstractCaosDBServerResource { try { getEntity(specifier).checkPermission(EntityPermission.RETRIEVE_FILE); } catch (EntityDoesNotExistException exception) { - // This file in the file system has no corresponding File record. - return error(ServerMessages.NOT_PERMITTED, Status.CLIENT_ERROR_FORBIDDEN); + // This file in the file system has no corresponding File record. It + // shall not be retrieved by anyone. + return error(ServerMessages.AUTHORIZATION_ERROR, Status.CLIENT_ERROR_FORBIDDEN); } final MediaType mt = MediaType.valueOf(FileUtils.getMimeType(file)); diff --git a/src/main/java/caosdb/server/utils/ServerMessages.java b/src/main/java/caosdb/server/utils/ServerMessages.java index 124962182a114f05f7bfabee377841412594a828..1381f60f5418d70e5530bfd930deb821738e21b1 100644 --- a/src/main/java/caosdb/server/utils/ServerMessages.java +++ b/src/main/java/caosdb/server/utils/ServerMessages.java @@ -26,6 +26,9 @@ 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."); @@ -133,8 +136,6 @@ public class ServerMessages { 0, "Cannot parse value to boolean (either 'true' or 'false, ignoring case)."); - public static final Message NOT_PERMITTED = new Message(MessageType.Error, 0, "Not permitted."); - public static final Message CANNOT_CONNECT_TO_DATABASE = new Message(MessageType.Error, 0, "Could not connect to MySQL server."); diff --git a/src/test/java/caosdb/server/authentication/AuthTokenTest.java b/src/test/java/caosdb/server/authentication/AuthTokenTest.java index 442e7d2196cbac2691b0196b6830c780624f064f..75bbbb56a60b4510ea7496770256de2ee6148087 100644 --- a/src/test/java/caosdb/server/authentication/AuthTokenTest.java +++ b/src/test/java/caosdb/server/authentication/AuthTokenTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import caosdb.server.CaosDBServer; +import caosdb.server.ServerProperties; import caosdb.server.accessControl.AnonymousAuthenticationToken; import caosdb.server.accessControl.AuthenticationUtils; import caosdb.server.accessControl.OneTimeAuthenticationToken; @@ -33,6 +34,7 @@ import caosdb.server.accessControl.OneTimeAuthenticationToken.Config; import caosdb.server.accessControl.Principal; import caosdb.server.accessControl.SelfValidatingAuthenticationToken; import caosdb.server.accessControl.SessionToken; +import caosdb.server.accessControl.SessionTokenRealm; import caosdb.server.database.BackendTransaction; import caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl; import caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl; @@ -46,9 +48,11 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; +import java.util.List; import java.util.Map; import org.apache.commons.io.input.CharSequenceInputStream; import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.config.Ini; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; @@ -227,17 +231,121 @@ public class AuthTokenTest { 60000, curry, new String[] {"permissions"}, - new String[] {"roles"}); - System.err.println(t1.toString()); + new String[] {"roles"}, + 1L); + Assert.assertEquals(1L, t1.getMaxAttempts()); Assert.assertFalse(t1.isExpired()); Assert.assertTrue(t1.isHashValid()); Assert.assertTrue(t1.isValid()); + String serialized = t1.toString(); + SelfValidatingAuthenticationToken parsed = OneTimeAuthenticationToken.parse(serialized, curry); + + Assert.assertEquals(t1, parsed); + Assert.assertEquals(serialized, parsed.toString()); + + Assert.assertEquals(1L, t1.getMaxAttempts()); + Assert.assertFalse(parsed.isExpired()); + Assert.assertTrue(parsed.isHashValid()); + Assert.assertTrue(parsed.isValid()); + } + + @Test(expected = AuthenticationException.class) + public void testOneTimeTokenConsume() { + final String curry = null; + final OneTimeAuthenticationToken t1 = + new OneTimeAuthenticationToken( + new Principal("somerealm", "someuser"), + 60000, + curry, + new String[] {"permissions"}, + new String[] {"roles"}, + 3L); + Assert.assertFalse(t1.isExpired()); + Assert.assertTrue(t1.isHashValid()); + Assert.assertTrue(t1.isValid()); + try { + t1.consume(); + t1.consume(); + t1.consume(); + } catch (AuthenticationException e) { + Assert.fail(e.getMessage()); + } + + // throws + t1.consume(); + Assert.fail("4th time consume() should throw"); + } + + @Test(expected = AuthenticationException.class) + public void testOneTimeTokenConsumeByParsing() { + final String curry = null; + final OneTimeAuthenticationToken t1 = + new OneTimeAuthenticationToken( + new Principal("somerealm", "someuser"), + 60000, + curry, + new String[] {"permissions"}, + new String[] {"roles"}, + 3L); + Assert.assertFalse(t1.isExpired()); + Assert.assertTrue(t1.isHashValid()); + Assert.assertTrue(t1.isValid()); + + String serialized = t1.toString(); + try { + SelfValidatingAuthenticationToken parsed1 = + OneTimeAuthenticationToken.parse(serialized, curry); + Assert.assertTrue(parsed1.isValid()); + SelfValidatingAuthenticationToken parsed2 = + OneTimeAuthenticationToken.parse(serialized, curry); + Assert.assertTrue(parsed2.isValid()); + SelfValidatingAuthenticationToken parsed3 = + OneTimeAuthenticationToken.parse(serialized, curry); + Assert.assertTrue(parsed3.isValid()); + } catch (AuthenticationException e) { + Assert.fail(e.getMessage()); + } + + // throws + OneTimeAuthenticationToken.parse(serialized, curry); + Assert.fail("4th parsing should throw"); + } + + @Test + public void testOneTimeTokenConfigEmpty() throws Exception { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("[]"); + + List<Config> configs = + OneTimeAuthenticationToken.loadConfig(new CharSequenceInputStream(testYaml, "utf-8")); + + Assert.assertTrue("empty config", configs.isEmpty()); + } + + @Test + public void testOneTimeTokenConfigDefaults() throws Exception { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("roles: []\n"); + + List<Config> configs = + OneTimeAuthenticationToken.loadConfig(new CharSequenceInputStream(testYaml, "utf-8")); + + Assert.assertEquals(1, configs.size()); + Assert.assertTrue("parsing to Config object", configs.get(0) instanceof Config); + + Config config = configs.get(0); + Assert.assertEquals( - t1.toString(), OneTimeAuthenticationToken.parse(t1.toString(), curry).toString()); - Assert.assertFalse(OneTimeAuthenticationToken.parse(t1.toString(), curry).isExpired()); - Assert.assertTrue(OneTimeAuthenticationToken.parse(t1.toString(), curry).isHashValid()); - Assert.assertTrue(OneTimeAuthenticationToken.parse(t1.toString(), curry).isValid()); + Integer.parseInt( + CaosDBServer.getServerProperty(ServerProperties.KEY_ACTIVATION_TIMEOUT_MS)), + config.getTimeout()); + Assert.assertEquals(1, config.getMaxAttempts()); + Assert.assertNull("no purpose", config.getPurpose()); + + Assert.assertArrayEquals("no permissions", new String[] {}, config.getPermissions()); + Assert.assertArrayEquals("no roles", new String[] {}, config.getRoles()); + Assert.assertNull("no output", config.getOutput()); } @Test @@ -245,6 +353,8 @@ public class AuthTokenTest { StringBuilder testYaml = new StringBuilder(); 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("permissions:\n"); testYaml.append(" - permission1\n"); testYaml.append(" - 'permission2'\n"); @@ -256,6 +366,8 @@ 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(3, config.getMaxAttempts()); Assert.assertArrayEquals( "permissions parsed", @@ -352,4 +464,24 @@ public class AuthTokenTest { OneTimeAuthenticationToken.generateForPurpose("for anonymous", anonymous, null); assertEquals("anonymous", token.getPrincipal().getUsername()); } + + @Test + public void testSessionTokenRealm() { + Config config = new Config(); + OneTimeAuthenticationToken token = OneTimeAuthenticationToken.generate(config); + + String serialized = token.toString(); + SelfValidatingAuthenticationToken parsed = + SelfValidatingAuthenticationToken.parse(serialized, null); + + SessionTokenRealm sessionTokenRealm = new SessionTokenRealm(); + Assert.assertTrue(sessionTokenRealm.supports(token)); + Assert.assertTrue(sessionTokenRealm.supports(parsed)); + + Assert.assertNotNull(sessionTokenRealm.getAuthenticationInfo(token)); + Assert.assertNotNull(sessionTokenRealm.getAuthenticationInfo(parsed)); + + Subject anonymous = SecurityUtils.getSubject(); + anonymous.login(token); + } }