diff --git a/src/main/java/caosdb/server/CaosAuthenticator.java b/src/main/java/caosdb/server/CaosAuthenticator.java index 25f112aa52050a0b872b2ff83e95400ad4bd925e..9272b41ecf45158440da841415882ef5d7969d31 100644 --- a/src/main/java/caosdb/server/CaosAuthenticator.java +++ b/src/main/java/caosdb/server/CaosAuthenticator.java @@ -23,11 +23,10 @@ package caosdb.server; import caosdb.server.accessControl.AuthenticationUtils; -import caosdb.server.accessControl.OneTimeAuthenticationToken; -import caosdb.server.accessControl.SessionToken; import caosdb.server.resource.DefaultResource; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.subject.Subject; import org.restlet.Context; import org.restlet.Request; @@ -48,12 +47,12 @@ public class CaosAuthenticator extends Authenticator { protected boolean authenticate(final Request request, final Response response) { final Subject subject = SecurityUtils.getSubject(); - return attemptOneTimeTokenLogin(subject, request) || attemptSessionValidation(subject, request); + return attemptSessionValidation(subject, request); } private static boolean attemptSessionValidation(final Subject subject, final Request request) { try { - final SessionToken sessionToken = + final AuthenticationToken sessionToken = AuthenticationUtils.parseSessionTokenCookie( request.getCookies().getFirst(AuthenticationUtils.SESSION_TOKEN_COOKIE), null); @@ -72,31 +71,6 @@ public class CaosAuthenticator extends Authenticator { return subject.isAuthenticated(); } - private static boolean attemptOneTimeTokenLogin(final Subject subject, final Request request) { - try { - OneTimeAuthenticationToken oneTimeToken = null; - - // try and parse from the query segment of the uri - oneTimeToken = - AuthenticationUtils.parseOneTimeTokenQuerySegment( - request.getResourceRef().getQueryAsForm().getFirstValue("AuthToken"), null); - - // try and parse from cookie - if (oneTimeToken == null) { - oneTimeToken = - AuthenticationUtils.parseOneTimeTokenCookie( - request.getCookies().getFirst(AuthenticationUtils.ONE_TIME_TOKEN_COOKIE), null); - } - - if (oneTimeToken != null) { - subject.login(oneTimeToken); - } - } catch (final AuthenticationException e) { - logger.info("LOGIN_FAILED", e); - } - return subject.isAuthenticated(); - } - @Override protected int unauthenticated(final Request request, final Response response) { final DefaultResource defaultResource = diff --git a/src/main/java/caosdb/server/CaosDBServer.java b/src/main/java/caosdb/server/CaosDBServer.java index 21685533b1f8c3ded743caf9c9c890c1f715cf75..ca1e64feec699407b4e35f642522f599440184fd 100644 --- a/src/main/java/caosdb/server/CaosDBServer.java +++ b/src/main/java/caosdb/server/CaosDBServer.java @@ -24,8 +24,6 @@ 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; import caosdb.server.accessControl.SessionTokenRealm; import caosdb.server.database.BackendTransaction; @@ -229,12 +227,11 @@ public class CaosDBServer extends Application { final Section mainSec = config.addSection("main"); mainSec.put("CaosDB", CaosDBDefaultRealm.class.getCanonicalName()); mainSec.put("SessionTokenValidator", SessionTokenRealm.class.getCanonicalName()); - mainSec.put("OneTimeTokenValidator", OneTimeTokenRealm.class.getCanonicalName()); mainSec.put("CaosDBAuthorizingRealm", CaosDBAuthorizingRealm.class.getCanonicalName()); mainSec.put("AnonymousRealm", AnonymousRealm.class.getCanonicalName()); mainSec.put( "securityManager.realms", - "$CaosDB, $SessionTokenValidator, $OneTimeTokenValidator, $CaosDBAuthorizingRealm, $AnonymousRealm"); + "$CaosDB, $SessionTokenValidator, $CaosDBAuthorizingRealm, $AnonymousRealm"); // disable shiro's default session management. We have quasi-stateless // sessions @@ -564,8 +561,7 @@ public class CaosDBServer extends Application { final Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated() && subject.getPrincipal() != AuthenticationUtils.ANONYMOUS_USER.getPrincipal()) { - final SessionToken sessionToken = - SessionToken.generate((Principal) subject.getPrincipal(), null); + final SessionToken sessionToken = SessionToken.generate(subject, null); // set session token cookie (httpOnly, secure cookie which // is used to recognize a user session) diff --git a/src/main/java/caosdb/server/accessControl/AnonymousAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/AnonymousAuthenticationToken.java index cd3f86f61eb66759b3eb7d0c91c29dc23637000b..f3f62af2af319d342977a7fe2524b353f8e7adab 100644 --- a/src/main/java/caosdb/server/accessControl/AnonymousAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/AnonymousAuthenticationToken.java @@ -28,7 +28,7 @@ public class AnonymousAuthenticationToken implements AuthenticationToken { private static final long serialVersionUID = 1424325396819592888L; private static final AnonymousAuthenticationToken INSTANCE = new AnonymousAuthenticationToken(); - public static final Object PRINCIPAL = new Object(); + public static final Principal PRINCIPAL = new Principal("anonymous", "anonymous"); private AnonymousAuthenticationToken() {} @@ -37,7 +37,7 @@ public class AnonymousAuthenticationToken implements AuthenticationToken { } @Override - public Object getPrincipal() { + public Principal getPrincipal() { return PRINCIPAL; } diff --git a/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java b/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java index 6e0fd5370ffcc2435067d68b3f2f810819ae9fbb..4a52c50196cd61242fd15e94409e8581eabda12a 100644 --- a/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java +++ b/src/main/java/caosdb/server/accessControl/AuthenticationUtils.java @@ -35,6 +35,7 @@ import java.sql.Timestamp; import java.util.Collection; import java.util.LinkedList; import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.subject.Subject; import org.restlet.data.Cookie; import org.restlet.data.CookieSetting; import org.slf4j.Logger; @@ -87,11 +88,8 @@ public class AuthenticationUtils { return null; } - public static CookieSetting createOneTimeTokenCookie(final OneTimeAuthenticationToken token) { - return createTokenCookie(AuthenticationUtils.ONE_TIME_TOKEN_COOKIE, token); - } - - public static CookieSetting createSessionTokenCookie(final SessionToken token) { + public static CookieSetting createSessionTokenCookie( + final SelfValidatingAuthenticationToken token) { return createTokenCookie(AuthenticationUtils.SESSION_TOKEN_COOKIE, token); } @@ -103,12 +101,13 @@ public class AuthenticationUtils { * @return A new SessionToken * @see {@link AuthenticationUtils#createSessionTokenCookie(SessionToken)}, {@link SessionToken} */ - public static SessionToken parseSessionTokenCookie(final Cookie cookie, final String curry) { + public static SelfValidatingAuthenticationToken parseSessionTokenCookie( + final Cookie cookie, final String curry) { if (cookie != null) { final String tokenString = URLDecodeWithUTF8(cookie.getValue()); if (tokenString != null && !tokenString.equals("")) { try { - return SessionToken.parse(tokenString, curry); + return SelfValidatingAuthenticationToken.parse(tokenString, curry); } catch (final Exception e) { logger.warn("AUTHTOKEN_PARSING_FAILED", e); } @@ -117,34 +116,6 @@ public class AuthenticationUtils { return null; } - private static OneTimeAuthenticationToken parseOneTimeToken( - final String tokenString, final String curry) { - if (tokenString != null && !tokenString.equals("")) { - try { - return OneTimeAuthenticationToken.parse(tokenString, curry); - } catch (final Exception e) { - logger.warn("AUTHTOKEN_PARSING_FAILED", e); - } - } - return null; - } - - public static OneTimeAuthenticationToken parseOneTimeTokenQuerySegment( - final String tokenString, final String curry) { - if (tokenString != null) { - return parseOneTimeToken(URLDecodeWithUTF8(tokenString), curry); - } - return null; - } - - public static OneTimeAuthenticationToken parseOneTimeTokenCookie( - final Cookie cookie, final String curry) { - if (cookie != null) { - return parseOneTimeToken(URLDecodeWithUTF8(cookie.getValue()), curry); - } - return null; - } - /** * Create a session timeout cookie. The value is a plain UTC timestamp which tells the user how * long his session will stay active. This cookie will be ignored by the server and carries only @@ -180,6 +151,7 @@ public class AuthenticationUtils { return null; } + // TODO move public static boolean isResponsibleAgentExistent(final ResponsibleAgent agent) { // 1) check OWNER, OTHER if (Role.OTHER_ROLE.equals(agent) || Role.OWNER_ROLE.equals(agent)) { @@ -227,4 +199,8 @@ public class AuthenticationUtils { false, false); } + + public static Collection<String> getRoles(Subject user) { + return new CaosDBAuthorizingRealm().doGetAuthorizationInfo(user.getPrincipals()).getRoles(); + } } diff --git a/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java b/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java index 5cfa425ac235405a0c861e54c9d97ae8ffab58f5..d715acf9c9cadebf9e5d7dde7b8ab3f3685fe231 100644 --- a/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java +++ b/src/main/java/caosdb/server/accessControl/CaosDBAuthorizingRealm.java @@ -22,9 +22,7 @@ */ package caosdb.server.accessControl; -import com.google.common.base.Objects; -import java.util.Arrays; -import java.util.List; +import java.util.Collection; import java.util.Set; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; @@ -32,100 +30,47 @@ import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; -import org.apache.shiro.subject.SimplePrincipalCollection; public class CaosDBAuthorizingRealm extends AuthorizingRealm { - private static class PermissionPrincipalCollection extends SimplePrincipalCollection { - - private final List<String> permissions; - - public PermissionPrincipalCollection( - final PrincipalCollection principals, final String[] permissions) { - super(principals); - this.permissions = Arrays.asList(permissions); - } - - private static final long serialVersionUID = 5585425107072564933L; - - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof PermissionPrincipalCollection) { - final PermissionPrincipalCollection that = (PermissionPrincipalCollection) obj; - return Objects.equal(that.permissions, this.permissions) && super.equals(that); - } else { - return false; - } - } + private static final CaosDBRolePermissionResolver role_permission_resolver = + new CaosDBRolePermissionResolver(); - @Override - public int hashCode() { - return super.hashCode() + 28 * this.permissions.hashCode(); - } + public Collection<String> getSessionRoles(SelfValidatingAuthenticationToken token) { + return token.getRoles(); } - static class PermissionAuthenticationInfo implements AuthenticationInfo { - - private static final long serialVersionUID = -3714484164124767976L; - private final PermissionPrincipalCollection principalCollection; - - public PermissionAuthenticationInfo( - final PrincipalCollection principals, final String... permissions) { - this.principalCollection = new PermissionPrincipalCollection(principals, permissions); - } - - @Override - public PrincipalCollection getPrincipals() { - return this.principalCollection; - } - - @Override - public Object getCredentials() { - return null; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof PermissionAuthenticationInfo) { - final PermissionAuthenticationInfo that = (PermissionAuthenticationInfo) obj; - return Objects.equal(that.principalCollection, this.principalCollection); - } else { - return false; - } - } - - @Override - public int hashCode() { - return this.principalCollection.hashCode(); - } + public Collection<String> getSessionPermissions(SelfValidatingAuthenticationToken token) { + return token.getPermissions(); } - private static final CaosDBRolePermissionResolver role_permission_resolver = - new CaosDBRolePermissionResolver(); - @Override protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) { - if (principals instanceof PermissionPrincipalCollection) { - // the PrincialsCollection carries the permissions. - final SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); - info.addStringPermissions(((PermissionPrincipalCollection) principals).permissions); - return info; - } - final SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo(); + Object principal = principals.getPrimaryPrincipal(); + + if (principal instanceof SelfValidatingAuthenticationToken) { + Collection<String> sessionPermissions = + getSessionPermissions((SelfValidatingAuthenticationToken) principal); + + Collection<String> sessionRoles = + getSessionRoles((SelfValidatingAuthenticationToken) principal); + + authzInfo.addRoles(sessionRoles); + authzInfo.addStringPermissions(sessionPermissions); + } // find all roles which are associated with this principal in this // realm. - final Set<String> roles = UserSources.resolve(principals); - if (roles != null) { - authzInfo.setRoles(roles); + final Set<String> principalRoles = + UserSources.resolve((Principal) principals.getPrimaryPrincipal()); + if (principalRoles != null) { + authzInfo.addRoles(principalRoles); + } - // find all permissions which are associated with these roles. - authzInfo.addObjectPermission(role_permission_resolver.resolvePermissionsInRole(roles)); + if (authzInfo.getRoles() != null) { + authzInfo.addObjectPermission( + role_permission_resolver.resolvePermissionsInRole(authzInfo.getRoles())); } return authzInfo; diff --git a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java index 0a0f71d654ec056e58b9d60f547e73d8624e2873..67e7be5e3171d4d967f7c83335e0512d8a8ac92e 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeAuthenticationToken.java @@ -36,16 +36,11 @@ 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; 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, final long date, @@ -55,109 +50,38 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke final String checksum, final String[] permissions, final String[] roles) { - super(principal, date, timeout, salt, curry, checksum); - this.permissions = permissions; - this.roles = roles; + super(principal, date, timeout, salt, curry, checksum, permissions, roles); } public OneTimeAuthenticationToken( final Principal principal, - final long date, final long timeout, - final String salt, final String curry, final String[] permissions, final String[] roles) { - this( - principal, - date, - timeout, - salt, - curry, - calcChecksum( - principal.getRealm(), - principal.getUsername(), - date, - timeout, - salt, - curry, - calcChecksum((Object[]) permissions), - calcChecksum((Object[]) roles), - PEPPER), - permissions, - roles); + super(principal, timeout, curry, permissions, roles); } private static final long serialVersionUID = -1072740888045267613L; - public String[] getPermissions() { - return this.permissions; - } - - public String[] getRoles() { - return this.roles; - } - - @Override - public String calcChecksum() { - return calcChecksum( - this.principal.getRealm(), - this.principal.getUsername(), - this.date, - this.timeout, - this.salt, - this.curry, - calcChecksum((Object[]) this.permissions), - calcChecksum((Object[]) this.roles), - PEPPER); - } - - @Override - public String toString() { - return JSON.toString( - new Object[] { - this.principal.getRealm(), - this.principal.getUsername(), - this.date, - this.timeout, - this.salt, - this.permissions, - this.roles, - this.checksum - }); - } - - private static String[] toStringArray(final Object[] array) { - final String[] ret = new String[array.length]; - for (int i = 0; i < ret.length; i++) { - ret[i] = (String) array[i]; - } - return ret; - } - - public static OneTimeAuthenticationToken parse(final String token, final String curry) { - final Object[] array = (Object[]) JSON.parse(token); - final Principal principal = new Principal((String) array[0], (String) array[1]); - final long date = (Long) array[2]; - final long timeout = (Long) array[3]; - final String salt = (String) array[4]; - final String[] permissions = toStringArray((Object[]) array[5]); - final String[] roles = toStringArray((Object[]) array[6]); - final String checksum = (String) array[7]; + 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]); + final String[] permissions = toStringArray((Object[]) array[4]); + final long date = (Long) array[5]; + final long timeout = (Long) array[6]; + final String salt = (String) array[7]; + final String checksum = (String) array[8]; return new OneTimeAuthenticationToken( principal, date, timeout, salt, curry, checksum, permissions, roles); } - public static OneTimeAuthenticationToken generate( + private static OneTimeAuthenticationToken generate( 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, - roles); + int timeout = + Integer.parseInt( + CaosDBServer.getServerProperty(ServerProperties.KEY_ACTIVATION_TIMEOUT_MS)); + return new OneTimeAuthenticationToken(principal, timeout, curry, permissions, roles); } public static List<Config> loadConfig(InputStream input) throws Exception { @@ -184,7 +108,7 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke } public static OneTimeAuthenticationToken generate(Config c) { - return generate(c, new Principal("anonymous", "anonymous"), null); + return generate(c, AnonymousAuthenticationToken.PRINCIPAL, null); } public static OneTimeAuthenticationToken generateForPurpose( @@ -287,4 +211,35 @@ public class OneTimeAuthenticationToken extends SelfValidatingAuthenticationToke init(f); } } + + @Override + public String calcChecksum(String pepper) { + return calcChecksum( + "O", + this.getRealm(), + this.getUsername(), + this.date, + this.timeout, + this.salt, + this.curry, + calcChecksum((Object[]) this.permissions), + calcChecksum((Object[]) this.roles), + pepper); + } + + @Override + public String toString() { + return JSON.toString( + new Object[] { + "O", + this.getRealm(), + this.getUsername(), + this.roles, + this.permissions, + this.date, + this.timeout, + this.salt, + this.checksum + }); + } } diff --git a/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java b/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java index 468a8d8d212ca5546f1940a4b3de38c1da50b3d8..1f7d4da4a45a75368fc0d76ae35ad06ad80da92e 100644 --- a/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java +++ b/src/main/java/caosdb/server/accessControl/OneTimeTokenRealm.java @@ -1,53 +1,55 @@ -/* - * ** 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 - * - * 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 - */ -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; - -public class OneTimeTokenRealm extends SessionTokenRealm { - - @Override - protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) - throws AuthenticationException { - - final AuthenticationInfo info = super.doGetAuthenticationInfo(token); - if (info != null) { - return new PermissionAuthenticationInfo( - info.getPrincipals(), ((OneTimeAuthenticationToken) token).getPermissions()); - } - - return null; - } - - public OneTimeTokenRealm() { - setAuthenticationTokenClass(OneTimeAuthenticationToken.class); - setCredentialsMatcher(new AllowAllCredentialsMatcher()); - setCachingEnabled(false); - setAuthenticationCachingEnabled(false); - // setAuthorizationCachingEnabled(false); - } -} +/// * +// * ** 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 +// * +// * 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 +// */ +// 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; +// +// public class OneTimeTokenRealm extends SessionTokenRealm { +// +// @Override +// protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token) +// throws AuthenticationException { +// +// final AuthenticationInfo info = super.doGetAuthenticationInfo(token); +// if (info != null) { +// return new PermissionAuthenticationInfo( +// info.getPrincipals(), +// ((OneTimeAuthenticationToken) token).getPermissions(), +// ((OneTimeAuthenticationToken) token).getRoles()); +// } +// +// return null; +// } +// +// public OneTimeTokenRealm() { +// setAuthenticationTokenClass(OneTimeAuthenticationToken.class); +// setCredentialsMatcher(new AllowAllCredentialsMatcher()); +// setCachingEnabled(false); +// setAuthenticationCachingEnabled(false); +// // setAuthorizationCachingEnabled(false); +// } +// } diff --git a/src/main/java/caosdb/server/accessControl/Principal.java b/src/main/java/caosdb/server/accessControl/Principal.java index 3183d864bd5baecc4429cf22fc0ab3ca75d8ca15..6a95dd79eccd50c9358928909822af67056102db 100644 --- a/src/main/java/caosdb/server/accessControl/Principal.java +++ b/src/main/java/caosdb/server/accessControl/Principal.java @@ -47,6 +47,11 @@ public class Principal implements ResponsibleAgent { this(split[0], split[1]); } + public Principal(Principal principal) { + this.username = principal.username; + this.realm = principal.realm; + } + public String getRealm() { return this.realm; } diff --git a/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java b/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java index 079f592ea7911f974bf4a64c9c5f1674e64a09a5..7d4cd846b5226e61856703c137310fd765643a4b 100644 --- a/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java +++ b/src/main/java/caosdb/server/accessControl/SelfValidatingAuthenticationToken.java @@ -23,17 +23,35 @@ package caosdb.server.accessControl; import caosdb.server.utils.Utils; +import java.util.Arrays; +import java.util.Collection; import org.apache.shiro.authc.AuthenticationToken; +import org.eclipse.jetty.util.ajax.JSON; -public abstract class SelfValidatingAuthenticationToken implements AuthenticationToken { +public abstract class SelfValidatingAuthenticationToken extends Principal + implements AuthenticationToken { + protected static final transient String PEPPER = Utils.getUID(); private static final long serialVersionUID = -7212039848895531161L; protected final long date; protected final long timeout; protected final String checksum; - protected final Principal principal; protected final String curry; protected final String salt; + protected final String[] permissions; + protected final String[] roles; + + public Collection<String> getPermissions() { + return Arrays.asList(this.permissions); + } + + public Collection<String> getRoles() { + return Arrays.asList(this.roles); + } + + public static final String getFreshSalt() { + return Utils.getUID(); + } public SelfValidatingAuthenticationToken( final Principal principal, @@ -41,34 +59,59 @@ public abstract class SelfValidatingAuthenticationToken implements Authenticatio final long timeout, final String salt, final String curry, - final String checksum) { - this.date = date; - this.timeout = timeout; - this.principal = principal; - this.salt = salt; - this.curry = curry; - this.checksum = checksum; + final String checksum, + final String[] permissions, + final String[] roles) { + this(principal, date, timeout, salt, curry, permissions, roles, checksum, false); } - public SelfValidatingAuthenticationToken( + private SelfValidatingAuthenticationToken( final Principal principal, final long date, final long timeout, final String salt, - final String curry) { + final String curry, + final String[] permissions, + final String[] roles, + String checksum, + boolean calcChecksum) { + super(principal); this.date = date; this.timeout = timeout; - this.principal = principal; this.salt = salt; this.curry = curry; - this.checksum = calcChecksum(); + this.permissions = permissions != null ? permissions : new String[] {}; + this.roles = roles != null ? roles : new String[] {}; + this.checksum = checksum == null && calcChecksum ? calcChecksum() : checksum; } - @Override - public Principal getPrincipal() { - return this.principal; + public SelfValidatingAuthenticationToken( + final Principal principal, + final long timeout, + final String curry, + final String[] permissions, + final String[] roles) { + this( + principal, + System.currentTimeMillis(), + timeout, + getFreshSalt(), + curry, + permissions, + roles, + null, + true); + } + + public String calcChecksum() { + return calcChecksum(PEPPER); } + @Override + public abstract String toString(); + + public abstract String calcChecksum(String pepper); + @Override public Object getCredentials() { return null; @@ -95,8 +138,6 @@ public abstract class SelfValidatingAuthenticationToken implements Authenticatio return !isExpired() && isHashValid(); } - public abstract String calcChecksum(); - protected static String calcChecksum(final Object... fields) { final StringBuilder sb = new StringBuilder(); for (final Object field : fields) { @@ -107,4 +148,27 @@ public abstract class SelfValidatingAuthenticationToken implements Authenticatio } return Utils.sha512(sb.toString(), null, 1); } + + protected static String[] toStringArray(final Object[] array) { + final String[] ret = new String[array.length]; + for (int i = 0; i < ret.length; i++) { + ret[i] = (String) array[i]; + } + return ret; + } + + public static SelfValidatingAuthenticationToken parse(String token, String curry) { + Object[] array = (Object[]) JSON.parse(token); + switch (array[0].toString()) { + case "O": + return OneTimeAuthenticationToken.parse(array, curry); + default: + return SessionToken.parse(array, curry); + } + } + + @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 273bd50ba7c4ef89fecd0e2b9b8817acbf6d9efa..695e4639c065a2b0ee2d31f05e3dce36cf3a54d3 100644 --- a/src/main/java/caosdb/server/accessControl/SessionToken.java +++ b/src/main/java/caosdb/server/accessControl/SessionToken.java @@ -24,86 +24,96 @@ package caosdb.server.accessControl; import caosdb.server.CaosDBServer; import caosdb.server.ServerProperties; +import org.apache.shiro.subject.Subject; import org.eclipse.jetty.util.ajax.JSON; public class SessionToken extends SelfValidatingAuthenticationToken { - /** - * Cryptographic pepper. Generated when class is loaded and used until the server reboots. Hence - * all SessionTokens invalidate when the server reboots. - */ - private static final transient String PEPPER = java.util.UUID.randomUUID().toString(); - - public static final String SEP = ":"; - public SessionToken( final Principal principal, final long date, final long timeout, final String salt, final String curry, - final String checksum) { - super(principal, date, timeout, salt, curry, checksum); + final String checksum, + final String[] permissions, + final String[] roles) { + super(principal, date, timeout, salt, curry, checksum, permissions, roles); } public SessionToken( final Principal principal, - final long date, final long timeout, - final String salt, - final String curry) { - super(principal, date, timeout, salt, curry); + final String curry, + final String[] permissions, + final String[] roles) { + super(principal, timeout, curry, permissions, roles); } private static final long serialVersionUID = 5887135104218573761L; + public static SessionToken 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]); + final String[] permissions = toStringArray((Object[]) array[4]); + final long date = (Long) array[5]; + final long timeout = (Long) array[6]; + final String salt = (String) array[7]; + final String checksum = (String) array[8]; + return new SessionToken(principal, date, timeout, salt, curry, checksum, permissions, roles); + } + + private static SessionToken generate( + final Principal principal, + final String curry, + final String[] permissions, + final String[] roles) { + int timeout = + Integer.parseInt(CaosDBServer.getServerProperty(ServerProperties.KEY_SESSION_TIMEOUT_MS)); + + return new SessionToken(principal, timeout, curry, permissions, roles); + } + + public static SessionToken generate(Subject subject, String curry) { + String[] permissions = new String[] {}; + String[] roles = new String[] {}; + if (subject.getPrincipal() instanceof SelfValidatingAuthenticationToken) { + SelfValidatingAuthenticationToken p = + (SelfValidatingAuthenticationToken) subject.getPrincipal(); + permissions = p.getPermissions().toArray(permissions); + roles = p.getRoles().toArray(roles); + } + return generate((Principal) subject.getPrincipal(), curry, permissions, roles); + } + @Override - public String calcChecksum() { + public String calcChecksum(String pepper) { return calcChecksum( + "S", + this.getRealm(), + this.getUsername(), this.date, this.timeout, - this.principal.getRealm(), - this.principal.getUsername(), - PEPPER, this.salt, - this.curry); + this.curry, + calcChecksum((Object[]) this.permissions), + calcChecksum((Object[]) this.roles), + pepper); } @Override public String toString() { return JSON.toString( new Object[] { - this.principal.getRealm(), - this.principal.getUsername(), + "S", + this.getRealm(), + this.getUsername(), + this.roles, + this.permissions, this.date, this.timeout, this.salt, this.checksum }); } - - public static SessionToken parse(final String token, final String curry) { - final Object[] array = (Object[]) JSON.parse(token); - final Principal principal = new Principal((String) array[0], (String) array[1]); - final long date = (Long) array[2]; - final long timeout = (Long) array[3]; - final String salt = (String) array[4]; - final String checksum = (String) array[5]; - return new SessionToken(principal, date, timeout, salt, curry, checksum); - } - - public static SessionToken generate(final Principal principal, final String curry) { - final SessionToken ret = - new SessionToken( - principal, - System.currentTimeMillis(), - Long.parseLong( - CaosDBServer.getServerProperty(ServerProperties.KEY_SESSION_TIMEOUT_MS).trim()), - java.util.UUID.randomUUID().toString(), - curry); - if (!ret.isValid()) { - throw new RuntimeException("SessionToken not valid!"); - } - return ret; - } } diff --git a/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java b/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java index 6ee72d0295153051e5ad31a6fd0fa092ab53d6e3..ff7e59b6ea4575a3ad95264c4f3dfbbcde41b78b 100644 --- a/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java +++ b/src/main/java/caosdb/server/accessControl/SessionTokenRealm.java @@ -37,7 +37,7 @@ public class SessionTokenRealm extends AuthenticatingRealm { (SelfValidatingAuthenticationToken) token; if (sessionToken.isValid()) { - return new SimpleAuthenticationInfo(sessionToken.getPrincipal(), null, getName()); + return new SimpleAuthenticationInfo(sessionToken, null, getName()); } return null; } diff --git a/src/main/java/caosdb/server/accessControl/UserSources.java b/src/main/java/caosdb/server/accessControl/UserSources.java index d0f707ebaaec8aa97e9b87d760fc0631cbd2f72a..b684570748412fa3cc8bea64c94a110726badac0 100644 --- a/src/main/java/caosdb/server/accessControl/UserSources.java +++ b/src/main/java/caosdb/server/accessControl/UserSources.java @@ -37,7 +37,6 @@ import java.util.HashSet; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.config.Ini; -import org.apache.shiro.subject.PrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -129,13 +128,16 @@ public class UserSources extends HashMap<String, UserSource> { * @return */ public static Set<String> resolve(String realm, final String username) { - if (realm == null) { realm = guessRealm(username); } + UserSource userSource = instance.get(realm); + if (userSource == null) { + return null; + } // find all roles that are associated with this principal in this realm - final Set<String> ret = instance.get(realm).resolveRolesForUsername(username); + final Set<String> ret = userSource.resolveRolesForUsername(username); return ret; } @@ -168,16 +170,15 @@ public class UserSources extends HashMap<String, UserSource> { return instance.map.getSectionProperty(Ini.DEFAULT_SECTION_NAME, KEY_DEFAULT_REALM); } - public static Set<String> resolve(final PrincipalCollection principals) { - if (principals.getPrimaryPrincipal() == AuthenticationUtils.ANONYMOUS_USER.getPrincipal()) { + public static Set<String> resolve(final Principal principal) { + if (principal == AuthenticationUtils.ANONYMOUS_USER.getPrincipal()) { // anymous has one role Set<String> roles = new HashSet<>(); roles.add(ANONYMOUS_ROLE); return roles; } - Principal primaryPrincipal = (Principal) principals.getPrimaryPrincipal(); - return resolve(primaryPrincipal.getRealm(), primaryPrincipal.getUsername()); + return resolve(principal.getRealm(), principal.getUsername()); } public static boolean isRoleExisting(final String role) { diff --git a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java index ea4e65f0a60d72cb5da6cb03b2cec44848dbc3c6..013501a0f39b24638c27a162e24015c168f13f3e 100644 --- a/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java +++ b/src/main/java/caosdb/server/resource/AbstractCaosDBServerResource.java @@ -29,7 +29,6 @@ import static java.net.URLDecoder.decode; import caosdb.server.CaosDBException; import caosdb.server.accessControl.AuthenticationUtils; import caosdb.server.accessControl.Principal; -import caosdb.server.accessControl.UserSources; import caosdb.server.database.backend.implementation.MySQL.ConnectionException; import caosdb.server.entity.Message; import caosdb.server.utils.ServerMessages; @@ -231,7 +230,7 @@ public abstract class AbstractCaosDBServerResource extends ServerResource { * @param user */ private void addRoles(Element userInfo, Subject user) { - Collection<String> roles = UserSources.resolve(user.getPrincipals()); + Collection<String> roles = AuthenticationUtils.getRoles(user); if (roles == null) return; Element rs = new Element("Roles"); for (String role : roles) { diff --git a/src/main/java/caosdb/server/resource/ScriptingResource.java b/src/main/java/caosdb/server/resource/ScriptingResource.java index 75baec81a1641748fbf27e300e7d01cc37beb63d..68e2203e9e24a8332a3629db82dce09a0abc552e 100644 --- a/src/main/java/caosdb/server/resource/ScriptingResource.java +++ b/src/main/java/caosdb/server/resource/ScriptingResource.java @@ -26,7 +26,6 @@ package caosdb.server.resource; 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; @@ -215,7 +214,7 @@ public class ScriptingResource extends AbstractCaosDBServerResource { if (authtoken != null || isAnonymous()) { return authtoken; } - return SessionToken.generate((Principal) getUser().getPrincipal(), null); + return SessionToken.generate(getUser(), null); } public void checkExecutionPermission(Subject user, String call) { diff --git a/src/main/java/caosdb/server/utils/Utils.java b/src/main/java/caosdb/server/utils/Utils.java index 6c48906a40fd299379e91446516a9c0325200f81..3be0ea6e9c1ad613a04bb8f5ad5c7b7ee13e2f88 100644 --- a/src/main/java/caosdb/server/utils/Utils.java +++ b/src/main/java/caosdb/server/utils/Utils.java @@ -33,7 +33,6 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.text.DecimalFormat; -import java.util.Random; import java.util.Scanner; import java.util.regex.Pattern; import org.apache.commons.codec.binary.Base32; @@ -45,9 +44,6 @@ import org.jdom2.output.XMLOutputter; /** Utility functions. */ public class Utils { - /** Random number generator initialized with the system time and used for generating UIDs. */ - private static Random rand = new Random(System.currentTimeMillis()); - /** Secure random number generator, for secret random numbers. */ private static final SecureRandom srand = new SecureRandom(); @@ -185,9 +181,7 @@ public class Utils { * @return The UID as a String. */ public static String getUID() { - synchronized (rand) { - return Long.toHexString(rand.nextLong()) + Long.toHexString(rand.nextLong()); - } + return Long.toHexString(srand.nextLong()) + Long.toHexString(srand.nextLong()); } /** diff --git a/src/test/java/caosdb/server/authentication/AuthTokenTest.java b/src/test/java/caosdb/server/authentication/AuthTokenTest.java index 288b405c6910eaf30aafb266383d11a4b8b4be35..442e7d2196cbac2691b0196b6830c780624f064f 100644 --- a/src/test/java/caosdb/server/authentication/AuthTokenTest.java +++ b/src/test/java/caosdb/server/authentication/AuthTokenTest.java @@ -26,17 +26,34 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import caosdb.server.CaosDBServer; +import caosdb.server.accessControl.AnonymousAuthenticationToken; 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.SelfValidatingAuthenticationToken; import caosdb.server.accessControl.SessionToken; +import caosdb.server.database.BackendTransaction; +import caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl; +import caosdb.server.database.backend.interfaces.RetrievePermissionRulesImpl; +import caosdb.server.database.backend.interfaces.RetrieveRoleImpl; +import caosdb.server.database.backend.interfaces.RetrieveUserImpl; +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 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.apache.shiro.SecurityUtils; +import org.apache.shiro.config.Ini; +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.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -48,6 +65,23 @@ public class AuthTokenTest { CaosDBServer.initServerProperties(); } + @BeforeClass + public static void setupShiro() throws IOException { + BackendTransaction.setImpl(RetrieveRoleImpl.class, RetrieveRole.class); + BackendTransaction.setImpl(RetrievePermissionRulesImpl.class, RetrievePermissionRules.class); + BackendTransaction.setImpl(RetrieveUserImpl.class, RetrieveUser.class); + BackendTransaction.setImpl( + RetrievePasswordValidatorImpl.class, RetrievePasswordValidator.class); + + CaosDBServer.initServerProperties(); + Ini config = CaosDBServer.getShiroConfig(); + final Factory<SecurityManager> factory = new IniSecurityManagerFactory(config); + + final SecurityManager securityManager = factory.getInstance(); + + SecurityUtils.setSecurityManager(securityManager); + } + @Test public void testSessionToken() throws InterruptedException { final String curry = "somecurry"; @@ -58,7 +92,9 @@ public class AuthTokenTest { 60000, "345sdf56sdf", curry, - "wrong checksum"); + "wrong checksum", + null, + null); Assert.assertFalse(t1.isExpired()); Assert.assertFalse(t1.isHashValid()); Assert.assertFalse(t1.isValid()); @@ -70,40 +106,27 @@ public class AuthTokenTest { 60000, "345sdf56sdf", null, - "wrong checksum"); + "wrong checksum", + null, + null); Assert.assertFalse(t2.isExpired()); Assert.assertFalse(t2.isHashValid()); Assert.assertFalse(t2.isValid()); final SessionToken t3 = - new SessionToken( - new Principal("somerealm", "someuser2"), - System.currentTimeMillis(), - 60000, - "72723gsdg", - curry); + new SessionToken(new Principal("somerealm", "someuser2"), 60000, curry, null, null); Assert.assertFalse(t3.isExpired()); Assert.assertTrue(t3.isHashValid()); Assert.assertTrue(t3.isValid()); final SessionToken t4 = - new SessionToken( - new Principal("somerealm", "someuser2"), - System.currentTimeMillis(), - 60000, - "72723gsdg", - null); + new SessionToken(new Principal("somerealm", "someuser2"), 60000, null, null, null); Assert.assertFalse(t4.isExpired()); Assert.assertTrue(t4.isHashValid()); Assert.assertTrue(t4.isValid()); final SessionToken t5 = - new SessionToken( - new Principal("somerealm", "someuser3"), - System.currentTimeMillis(), - 2000, - "23sdfsg34", - curry); + new SessionToken(new Principal("somerealm", "someuser3"), 2000, curry, null, null); Assert.assertFalse(t5.isExpired()); Assert.assertTrue(t5.isHashValid()); Assert.assertTrue(t5.isValid()); @@ -114,12 +137,7 @@ public class AuthTokenTest { Assert.assertFalse(t5.isValid()); final SessionToken t6 = - new SessionToken( - new Principal("somerealm", "someuser3"), - System.currentTimeMillis(), - 0, - "23sdfsg34", - null); + new SessionToken(new Principal("somerealm", "someuser3"), 0, null, null, null); Assert.assertTrue(t6.isExpired()); Assert.assertTrue(t6.isHashValid()); Assert.assertFalse(t6.isValid()); @@ -206,9 +224,7 @@ public class AuthTokenTest { final OneTimeAuthenticationToken t1 = new OneTimeAuthenticationToken( new Principal("somerealm", "someuser"), - System.currentTimeMillis(), - 60000L, - "sdfh37456sd", + 60000, curry, new String[] {"permissions"}, new String[] {"roles"}); @@ -307,13 +323,33 @@ public class AuthTokenTest { 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); + OneTimeAuthenticationToken token = + (OneTimeAuthenticationToken) + SelfValidatingAuthenticationToken.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()); + token.getPermissions().toArray()); } } + + @Test + public void testOneTimeTokenForAnonymous() throws Exception { + StringBuilder testYaml = new StringBuilder(); + testYaml.append("purpose: for anonymous\n"); + testYaml.append("roles: [ role1 ]\n"); + testYaml.append("permissions:\n"); + testYaml.append(" - permission1\n"); + + OneTimeAuthenticationToken.init(new CharSequenceInputStream(testYaml, "utf-8")); + + Subject anonymous = SecurityUtils.getSubject(); + anonymous.login(AnonymousAuthenticationToken.getInstance()); + + OneTimeAuthenticationToken token = + OneTimeAuthenticationToken.generateForPurpose("for anonymous", anonymous, null); + assertEquals("anonymous", token.getPrincipal().getUsername()); + } } diff --git a/src/test/java/caosdb/server/resource/TestScriptingResource.java b/src/test/java/caosdb/server/resource/TestScriptingResource.java index b9797eb85ef779a2b957f65a5c96b56787d48421..ddf915fb2bc8f9401c630339b274404ab37545af 100644 --- a/src/test/java/caosdb/server/resource/TestScriptingResource.java +++ b/src/test/java/caosdb/server/resource/TestScriptingResource.java @@ -29,7 +29,6 @@ import caosdb.server.accessControl.AuthenticationUtils; import caosdb.server.accessControl.CredentialsValidator; import caosdb.server.accessControl.Principal; import caosdb.server.accessControl.Role; -import caosdb.server.accessControl.SessionToken; import caosdb.server.database.BackendTransaction; import caosdb.server.database.access.Access; import caosdb.server.database.backend.interfaces.RetrievePasswordValidatorImpl; @@ -201,12 +200,6 @@ public class TestScriptingResource { @Test public void testUnsupportedMediaType() { - Subject user = SecurityUtils.getSubject(); - if (user.isAuthenticated()) { - user.logout(); - } - SessionToken t = SessionToken.generate(new Principal("CaosDB", "user"), null); - user.login(t); Representation entity = new StringRepresentation("asdf"); entity.setMediaType(MediaType.TEXT_ALL); Request request = new Request(Method.POST, "../test", entity);