diff --git a/README.md b/README.md index d22750c..c048898 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## Description -A valve for Tomcat8 that authenticates the JWT tokens created by Islandora in order to provide sessionless Authentication for Fedora4. Named after the Norse goddess [Syn](https://en.wikipedia.org/wiki/Syn_(goddess)). +A ServletFilter that authenticates the JWT tokens created by Islandora in order to provide sessionless Authentication for Fedora4. Named after the Norse goddess [Syn](https://en.wikipedia.org/wiki/Syn_(goddess)). ## Building @@ -15,54 +15,39 @@ This project requires Java 8 and can be built with [Gradle](https://gradle.org). ## Installing ### Copy Syn JAR -Copy the JAR that was built above from `build/libs/islandora-syn-X.X.X-all.jar` and place into `$TOMCAT_HOME/lib` directory. Can be found in Ubuntu at: `/var/lib/tomcat8/lib/`. Note that this JAR is built to contain all the dependancies. +Copy the JAR that was built above from `build/libs/islandora-syn-X.X.X-all.jar` and place into `$TOMCAT_HOME/lib` directory or the individual webapps `WEB-INF/lib` directory. Can be found in Ubuntu at: `/var/lib/tomcat8/lib/`. Note that this JAR is built to contain all the dependancies. -### Register Valve -Now register the valve in Tomcat configuration file. -In Ubuntu this file is located at: `/var/lib/tomcat8/conf/context.xml` +### Register Filter +Now register the filter in web applications' `web.xml` file by adding something like. ```xml - + + SynFilter + ca.islandora.syn.valve.SynFilter + + settings-path + /var/lib/tomcat8/conf/syn-settings.yml + + + + + SynFilter + /* + ``` -where: -* ***pathname***: The location of the settings file. Defaults to `$CATALINA_BASE/conf/syn-settings.xml`. - -### Enable `security-contraint` -The valve checks if requested url is under **security contraints**. So, valve will activate only if the Fedora4 *web.xml* file contains something like: - -```xml - - - Fedora4 - /* - - - * - - - NONE - - - - islandora - - - BASIC - fcrepo - -``` +Where the **settings-path** `param-value` is the the location of the settings file. On ubuntu this file can be found at: `/var/lib/tomcat8/webapps/fcrepo/WEB-INF/web.xml` ### Setup Syn Configuration -Modify the [example configuration](./conf/syn-settings.example.xml) and move it to: `$CATALINA_BASE/conf/syn-settings.xml`. +Modify the [example configuration](./conf/syn-settings.example.yaml) and move it to: `$CATALINA_BASE/conf/syn-settings.xml`. Then use this path when configuring the application's filter `init-param`s. ## Maintainers * [Jonathan Green](https://github.com/jonathangreen/) +* [Jared Whiklo](https://github.com/whikloj) ## Development diff --git a/build.gradle b/build.gradle index 7b29848..d99d80d 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,20 @@ targetCompatibility = 1.8 def tomcatVersion = '8.0.28' +ext { + + versions = [ + bcprov : '1.56', + jackson : '2.9.2', + javaJwt : '3.1.0', + junit : '4.12', + logback : '1.0.13', + mockito : '2.7.14', + servlet : '3.0.1', + slf4j : '1.7.12' + ] +} + checkstyle { configFile = rootProject.file('gradle/checkstyle/checkstyle.xml') configProperties.checkstyleConfigDir = rootProject.file('gradle/checkstyle') @@ -24,15 +38,17 @@ repositories { } dependencies { - compile group: 'com.auth0', name: 'java-jwt', version:'3.1.0' - compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version:'1.56' - compileOnly group: 'org.apache.tomcat', name: 'tomcat-catalina', version:tomcatVersion - compileOnly group: 'org.apache.tomcat', name: 'tomcat-coyote', version:tomcatVersion - - testCompile group: 'junit', name: 'junit', version:'4.12' - testCompile group: 'org.mockito', name: 'mockito-core', version:'2.7.14' - testCompile group: 'org.apache.tomcat', name: 'tomcat-catalina', version:tomcatVersion - testCompile group: 'org.apache.tomcat', name: 'tomcat-coyote', version:tomcatVersion + compile group: 'com.auth0', name: 'java-jwt', version: versions.javaJwt + compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: versions.bcprov + compile group: 'org.slf4j', name: 'slf4j-api', version: versions.slf4j + compile group: 'javax.servlet', name: 'javax.servlet-api', version: versions.servlet + compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: versions.jackson + compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: versions.jackson + + testRuntime group: 'ch.qos.logback', name: 'logback-classic', version: versions.logback + + testCompile group: 'junit', name: 'junit', version: versions.junit + testCompile group: 'org.mockito', name: 'mockito-core', version: versions.mockito } jacocoTestReport { @@ -54,6 +70,13 @@ buildscript { jar { baseName = projectName version = projectVersion + manifest { + attributes("Implementation-Title": baseName, + "Implementation-Version": version, + "Implementation-Vendor": "Islandora", + "Class-Path": configurations.compile.collect { it.getName() }.join(' ')) + } + } shadowJar { @@ -62,3 +85,14 @@ shadowJar { } assemble.dependsOn(shadowJar); + +test { + testLogging { + // Make sure output from + // standard out or error is shown + // in Gradle output. + // showStandardStreams = true + //events 'standard_out' + exceptionFormat = 'full' + } +} \ No newline at end of file diff --git a/conf/syn-settings.example.xml b/conf/syn-settings.example.xml deleted file mode 100644 index 3cbd328..0000000 --- a/conf/syn-settings.example.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - -my secret key - - - - - - - - - - - - - - my super secret token - - - diff --git a/conf/syn-settings.example.yaml b/conf/syn-settings.example.yaml new file mode 100644 index 0000000..a1ea934 --- /dev/null +++ b/conf/syn-settings.example.yaml @@ -0,0 +1,43 @@ +version: 1 +# Sites can be specified with a Key inline, or with a reference to a key +# stored in a file. Both are shown in examples below. +# +# The encoding parameter depends on what algorithm is chosen. +# HS256, HS384, HS512 support: plain and base64. +# RS256, RS384, RS512 support: PEM. + +# A site with an inline key +site: + url: http://test.com + algorithm: HS256 + encoding: plain + key: my secret key + +# A site with a key stored in a file +site: + url: http://test2.com + algorithm: HS256 + encoding: base64 + path: /somewhere/on/filesystem.key + +# A site that allows all GET/HEAD requests +site: + url: http://test3.com + algorithm: HS256 + encoding: plain + anonymous: true + +# This is how you specify a default site, which will be chosen if no +# other site matches the JWT url claim +site: + algorithm: RS256 + encoding: PEM + path: /somewhere/on/filesystem.key + default: true + +# This lets you specify a master token for testing. This should be used with care, as it gives anyone +# with this token unlimited access to your repository. +token: + user: test + roles: role1,role2,role3 + value: my super secret token diff --git a/src/main/java/ca/islandora/syn/settings/Config.java b/src/main/java/ca/islandora/syn/settings/Config.java index de74243..b96a38b 100644 --- a/src/main/java/ca/islandora/syn/settings/Config.java +++ b/src/main/java/ca/islandora/syn/settings/Config.java @@ -3,29 +3,37 @@ import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonSetter; + public class Config { private int version = -1; - private List sites = new ArrayList<>(); - private List tokens = new ArrayList<>(); + private final List sites = new ArrayList<>(); + private final List tokens = new ArrayList<>(); + @JsonSetter("site") public void addSite(final Site site) { - sites.add(site); + this.sites.add(site); } + public List getSites() { - return sites; + return this.sites; } public int getVersion() { return this.version; } + public void setVersion(final int version) { this.version = version; } + @JsonSetter("token") public void addToken(final Token token) { - tokens.add(token); + this.tokens.add(token); } + public List getTokens() { - return tokens; + return this.tokens; } + } diff --git a/src/main/java/ca/islandora/syn/settings/SettingsParser.java b/src/main/java/ca/islandora/syn/settings/SettingsParser.java index 92e25b5..fa28ba8 100644 --- a/src/main/java/ca/islandora/syn/settings/SettingsParser.java +++ b/src/main/java/ca/islandora/syn/settings/SettingsParser.java @@ -1,10 +1,11 @@ package ca.islandora.syn.settings; +import static org.slf4j.LoggerFactory.getLogger; + import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.nio.file.Files; @@ -17,66 +18,88 @@ import java.util.Map; import java.util.stream.Collectors; -import org.apache.juli.logging.Log; -import org.apache.juli.logging.LogFactory; -import org.apache.tomcat.util.digester.Digester; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; -import org.xml.sax.SAXException; +import org.slf4j.Logger; import com.auth0.jwt.algorithms.Algorithm; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; public final class SettingsParser { - private static Digester digester = null; - private static Log log = LogFactory.getLog(Site.class); - private enum AlgorithmType {INVALID, RSA, HMAC} - - private SettingsParser() { } - - private static Digester getDigester() { - if (digester == null) { - digester = new Digester(); - digester.setValidating(false); - digester.addObjectCreate("config", "ca.islandora.syn.settings.Config"); - digester.addSetProperties("config"); - digester.addObjectCreate("config/site", "ca.islandora.syn.settings.Site"); - digester.addSetProperties("config/site"); - digester.addCallMethod("config/site", "setKey", 0); - digester.addSetNext("config/site", "addSite", "ca.islandora.syn.settings.Site"); - digester.addObjectCreate("config/token", "ca.islandora.syn.settings.Token"); - digester.addSetProperties("config/token"); - digester.addCallMethod("config/token", "setToken", 0); - digester.addSetNext("config/token", "addToken", "ca.islandora.syn.settings.Token"); + private static Logger log = getLogger(Site.class); + + private enum AlgorithmType { + INVALID, RSA, HMAC + } + + private static final int VALID_VERSION = 1; + + private Config loadedSites; + + private final static ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + /** + * Constructor. + * + * @param settings + * A Reader object with the configuration in it. + * @throws Exception + * On loading/parsing or version of configuration. + */ + public SettingsParser(final Reader settings) { + try { + loadedSites = mapper.readValue(settings, Config.class); + } catch (final Exception e) { + log.error("Error loading settings file.", e); + throw new SettingsParserException("Error parsing settings file.", e); + } + + if (loadedSites.getVersion() != VALID_VERSION) { + log.error("Incorrect config version. Aborting."); + throw new SettingsParserException("Incorrect config version. Aborting."); } - return digester; } + /** + * Static creator. + * + * @param settings + * A Reader object with the configuration on it. + * @return The SettingsParser. + * @throws Exception + * On loading/parsing or version of configuration. + */ + public static SettingsParser create(final Reader settings) { + return new SettingsParser(settings); + } + /** + * Determine the type of key algorithm. + * + * @param algorithm + * The algorithm name. + * @return an algorithm. + */ private static AlgorithmType getSiteAlgorithmType(final String algorithm) { - if (algorithm.equalsIgnoreCase("RS256")) { - return AlgorithmType.RSA; - } else if (algorithm.equalsIgnoreCase("RS384")) { + if (algorithm.toUpperCase().startsWith("RS")) { return AlgorithmType.RSA; - } else if (algorithm.equalsIgnoreCase("RS512")) { - return AlgorithmType.RSA; - } - - if (algorithm.equalsIgnoreCase("HS256")) { - return AlgorithmType.HMAC; - } else if (algorithm.equalsIgnoreCase("HS384")) { - return AlgorithmType.HMAC; - } else if (algorithm.equalsIgnoreCase("HS512")) { + } else if (algorithm.toUpperCase().startsWith("HS")) { return AlgorithmType.HMAC; } else { return AlgorithmType.INVALID; } } - private static boolean validateExpandPath(final Site site) { - File file = new File(site.getPath()); - if (!file.isAbsolute()) { - file = new File(System.getProperty("catalina.base"), site.getPath()); - } + /** + * Validate the site's key path. + * + * @param site + * The site to act on. + * @return true if key exists. + */ + private static boolean validatePath(final Site site) { + final File file = new File(site.getPath()); if (!file.exists() || !file.canRead()) { log.error("Path does not exist:" + site.getPath() + ". Site ignored."); return false; @@ -85,16 +108,23 @@ private static boolean validateExpandPath(final Site site) { return true; } + /** + * Parse a RSA encoded key and return the algorithm for verifying. + * + * @param site + * The site to get the key for. + * @return A RSA algorithm for the site's key. + */ private static Algorithm getRsaAlgorithm(final Site site) { Reader publicKeyReader = null; RSAPublicKey publicKey = null; - if (!site.getKey().equalsIgnoreCase("")) { + if (site.getKey() != null) { publicKeyReader = new StringReader(site.getKey()); } else if (site.getPath() != null) { try { publicKeyReader = new FileReader(site.getPath()); - } catch (FileNotFoundException e) { + } catch (final FileNotFoundException e) { log.error("Private key file not found."); } } @@ -112,7 +142,7 @@ private static Algorithm getRsaAlgorithm(final Site site) { publicKey = (RSAPublicKey) factory.generatePublic(pubKeySpec); pemReader.close(); publicKeyReader.close(); - } catch (Exception e) { + } catch (final Exception e) { log.error("Error loading public key."); return null; } @@ -133,16 +163,23 @@ private static Algorithm getRsaAlgorithm(final Site site) { } } + /** + * Parse a HMAC encoded key and return the algorithm for verifying. + * + * @param site + * The site to get the key for. + * @return A HMAC algorithm for the site's key. + */ private static Algorithm getHmacAlgorithm(final Site site) { - byte[] secret; + final byte[] secret; byte[] secretRaw = null; - if (!site.getKey().equalsIgnoreCase("")) { + if (site.getKey() != null) { secretRaw = site.getKey().trim().getBytes(); } else if (site.getPath() != null) { try { secretRaw = Files.readAllBytes(Paths.get(site.getPath())); - } catch (IOException e) { + } catch (final IOException e) { log.error("Unable to get secret from file.", e); } } @@ -154,7 +191,7 @@ private static Algorithm getHmacAlgorithm(final Site site) { if (site.getEncoding().equalsIgnoreCase("base64")) { try { secret = Base64.getDecoder().decode(secretRaw); - } catch (Exception e) { + } catch (final Exception e) { log.error("Base64 decode error. Skipping site.", e); return null; } @@ -175,34 +212,20 @@ private static Algorithm getHmacAlgorithm(final Site site) { } } - private static Config getSites(final InputStream settings) { - Config sites; - - try { - sites = getSitesObject(settings); - } catch (Exception e) { - log.error("Error loading settings file.", e); - return null; - } - - if (sites.getVersion() != 1) { - log.error("Incorrect XML version. Aborting."); - return null; - } - - return sites; - } - - public static Map getSiteAlgorithms(final InputStream settings) { + /** + * Get site keys from loaded Config. + * + * @return Map of URLs (or null) and parsed keys for verification. + */ + public Map getSiteAlgorithms() { final Map algorithms = new HashMap<>(); - final Config sites = getSites(settings); - if (sites == null) { + if (loadedSites == null) { return algorithms; } boolean defaultSet = false; - for (Site site : sites.getSites()) { + for (final Site site : loadedSites.getSites()) { final boolean pathDefined = site.getPath() != null && !site.getPath().equalsIgnoreCase(""); final boolean keyDefined = site.getKey() != null && !site.getKey().equalsIgnoreCase(""); @@ -213,20 +236,20 @@ public static Map getSiteAlgorithms(final InputStream setting } if (site.getPath() != null) { - if (!validateExpandPath(site)) { + if (!validatePath(site)) { continue; } } // Check that the algorithm type is valid. final AlgorithmType algorithmType = getSiteAlgorithmType(site.getAlgorithm()); - Algorithm algorithm; + final Algorithm algorithm; if (algorithmType == AlgorithmType.HMAC) { algorithm = getHmacAlgorithm(site); } else if (algorithmType == AlgorithmType.RSA) { algorithm = getRsaAlgorithm(site); } else { - log.error("Invalid algorithm selection: " + site.getAlgorithm() + ". Site ignored." ); + log.error("Invalid algorithm selection: " + site.getAlgorithm() + ". Site ignored."); continue; } @@ -252,14 +275,18 @@ public static Map getSiteAlgorithms(final InputStream setting return algorithms; } - public static Map getSiteStaticTokens(final InputStream settings) { - final Config sites = getSites(settings); - if (sites == null) { + /** + * Get static tokens from the loaded Config. + * + * @return Map of token value and Token object. + */ + public Map getSiteStaticTokens() { + if (loadedSites == null) { return new HashMap(); } - final Map tokens = sites.getTokens().stream().filter(x -> !x.getToken().isEmpty()) - .collect(Collectors.toMap(Token::getToken, t -> t)); + final Map tokens = loadedSites.getTokens().stream().filter(x -> !x.getValue().isEmpty()) + .collect(Collectors.toMap(Token::getValue, t -> t)); return tokens; } @@ -267,25 +294,28 @@ public static Map getSiteStaticTokens(final InputStream settings) /** * Build a list of site urls that allow anonymous GET requests. * - * @param settings the path to the syn-settings file - * @return list of site urls. + * @return Map of all URLs (or null for default) and boolean if they allow + * anonymous. */ - public static Map getSiteAllowAnonymous(final InputStream settings) { - final Config sites = getSites(settings); - if (sites == null) { + public Map getSiteAllowAnonymous() { + if (loadedSites == null) { return new HashMap(); } - final Map anonymousAllowed = sites.getSites().stream().filter(s -> !s.getDefault()) - .collect(Collectors.toMap(Site::getUrl, Site::getAnonymous)); - sites.getSites().stream().filter(Site::getDefault).findFirst() - .ifPresent(s -> anonymousAllowed.put("default", s.getAnonymous())); + final Map anonymousAllowed = loadedSites.getSites().stream().filter(s -> !s.getDefault()) + .collect(Collectors.toMap(Site::getUrl, Site::getAnonymous)); + loadedSites.getSites().stream().filter(Site::getDefault).findFirst() + .ifPresent(s -> anonymousAllowed.put("default", s.getAnonymous())); return anonymousAllowed; } - static Config getSitesObject(final InputStream settings) - throws IOException, SAXException { - return (Config) getDigester().parse(settings); + /** + * Getter for loaded Config object. + * + * @return + */ + public Config getConfig() { + return loadedSites; } } diff --git a/src/main/java/ca/islandora/syn/settings/SettingsParserException.java b/src/main/java/ca/islandora/syn/settings/SettingsParserException.java new file mode 100644 index 0000000..a61556c --- /dev/null +++ b/src/main/java/ca/islandora/syn/settings/SettingsParserException.java @@ -0,0 +1,35 @@ +package ca.islandora.syn.settings; + +/** + * Exception while parsing the settings YAML file. + * + * @author whikloj + * @since 2018-01-17 + */ +public class SettingsParserException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Constructor + * + * @param message + * Exception message + */ + public SettingsParserException(final String message) { + super(message); + } + + /** + * Constructor + * + * @param message + * Exception message + * @param e + * Wrapped Exception + */ + public SettingsParserException(final String message, final Throwable e) { + super(message, e); + } + +} diff --git a/src/main/java/ca/islandora/syn/settings/Site.java b/src/main/java/ca/islandora/syn/settings/Site.java index 1524282..4cf7f19 100644 --- a/src/main/java/ca/islandora/syn/settings/Site.java +++ b/src/main/java/ca/islandora/syn/settings/Site.java @@ -12,6 +12,7 @@ public class Site { public String getUrl() { return this.url; } + public void setUrl(final String url) { this.url = url; } @@ -19,6 +20,7 @@ public void setUrl(final String url) { public String getAlgorithm() { return this.algorithm; } + public void setAlgorithm(final String algorithm) { this.algorithm = algorithm; } @@ -26,6 +28,7 @@ public void setAlgorithm(final String algorithm) { public String getKey() { return this.key; } + public void setKey(final String key) { this.key = key; } @@ -33,6 +36,7 @@ public void setKey(final String key) { public String getPath() { return this.path; } + public void setPath(final String path) { this.path = path; } @@ -40,6 +44,7 @@ public void setPath(final String path) { public String getEncoding() { return this.encoding; } + public void setEncoding(final String encoding) { this.encoding = encoding; } @@ -47,6 +52,7 @@ public void setEncoding(final String encoding) { public boolean getDefault() { return this.defaultItem; } + public void setDefault(final boolean defaultItem) { this.defaultItem = defaultItem; } @@ -63,7 +69,8 @@ public boolean getAnonymous() { /** * Set allow GET requests with a token that match this site. * - * @param allowAnonGet boolean whether to allow these requests. + * @param allowAnonGet + * boolean whether to allow these requests. */ public void setAnonymous(final boolean allowAnonGet) { this.allowAnonymous = allowAnonGet; diff --git a/src/main/java/ca/islandora/syn/settings/Token.java b/src/main/java/ca/islandora/syn/settings/Token.java index 3548d72..630c6d1 100644 --- a/src/main/java/ca/islandora/syn/settings/Token.java +++ b/src/main/java/ca/islandora/syn/settings/Token.java @@ -6,8 +6,8 @@ public class Token { private String user = "islandoraAdmin"; - private List roles = new ArrayList<>(); - private String token = ""; + private final List roles = new ArrayList<>(); + private String value = ""; public String getUser() { return user; @@ -25,15 +25,15 @@ public void setRoles(final String roles) { this.roles.clear(); if (!roles.isEmpty()) { final String[] parts = roles.split(","); - Collections.addAll(this.roles,parts); + Collections.addAll(this.roles, parts); } } - public String getToken() { - return token; + public String getValue() { + return value; } - public void setToken(final String token) { - this.token = token.trim(); + public void setValue(final String token) { + this.value = token.trim(); } } \ No newline at end of file diff --git a/src/main/java/ca/islandora/syn/token/InvalidTokenException.java b/src/main/java/ca/islandora/syn/token/InvalidTokenException.java new file mode 100644 index 0000000..bdbde41 --- /dev/null +++ b/src/main/java/ca/islandora/syn/token/InvalidTokenException.java @@ -0,0 +1,36 @@ +package ca.islandora.syn.token; + +/** + * + * @author whikloj + * + */ +public class InvalidTokenException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Constructor + * + * @param message + * Exception message + */ + public InvalidTokenException(final String message) { + super(message); + } + + /** + * Constructor + * + * @param message + * Exception message + * @param e + * Wrapped Exception + */ + public InvalidTokenException(final String message, final Throwable e) { + super(message, e); + } +} diff --git a/src/main/java/ca/islandora/syn/token/Verifier.java b/src/main/java/ca/islandora/syn/token/Verifier.java index 97fe054..f4d5d5e 100644 --- a/src/main/java/ca/islandora/syn/token/Verifier.java +++ b/src/main/java/ca/islandora/syn/token/Verifier.java @@ -1,62 +1,60 @@ package ca.islandora.syn.token; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Arrays; import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; + import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTVerificationException; -import org.apache.juli.logging.Log; -import org.apache.juli.logging.LogFactory; +import com.auth0.jwt.interfaces.Claim; public class Verifier { - private static final Log log = LogFactory.getLog(Verifier.class); + private static final Logger log = getLogger(Verifier.class); private String token; private JWT jwt; - private Verifier() { } + private Verifier() { + } public static Verifier create(final String token) { final Verifier verifier = new Verifier(); + final List requiredClaims = Arrays.asList("sub", "iss", "webid", "roles", "exp", "iat"); verifier.token = token; try { verifier.jwt = JWT.decode(token); - if (verifier.jwt.getClaim("uid").isNull()) { - return null; - } - if (verifier.jwt.getClaim("url").isNull()) { - return null; + final Map claims = verifier.jwt.getClaims(); + for (final String claim : requiredClaims) { + if (claims.get(claim) == null) { + log.info("Token missing required claim ({})", claim); + throw new InvalidTokenException( + String.format("Token missing required claim ({})", claim)); + } } - if (verifier.jwt.getClaim("name").isNull()) { - return null; - } - if (verifier.jwt.getClaim("roles").isNull()) { - return null; - } - if (verifier.jwt.getExpiresAt() == null) { - return null; - } - if (verifier.jwt.getIssuedAt() == null) { - return null; - } - } catch (JWTDecodeException exception) { + } catch (final JWTDecodeException exception) { log.error("Error decoding token: " + token, exception); - return null; + throw new InvalidTokenException("Error decoding token: " + token, exception); } return verifier; } public int getUid() { - return this.jwt.getClaim("uid").asInt(); + return this.jwt.getClaim("webid").asInt(); } public String getUrl() { - return this.jwt.getClaim("url").asString(); + return this.jwt.getClaim("iss").asString(); } public String getName() { - return this.jwt.getClaim("name").asString(); + return this.jwt.getClaim("sub").asString(); } public List getRoles() { @@ -67,10 +65,11 @@ public boolean verify(final Algorithm algorithm) { final JWTVerifier verifier = JWT.require(algorithm).build(); try { verifier.verify(this.token); - } catch (JWTVerificationException exception) { + } catch (final JWTVerificationException exception) { return false; } return true; } + } diff --git a/src/main/java/ca/islandora/syn/valve/SynFilter.java b/src/main/java/ca/islandora/syn/valve/SynFilter.java new file mode 100644 index 0000000..a5dd0d5 --- /dev/null +++ b/src/main/java/ca/islandora/syn/valve/SynFilter.java @@ -0,0 +1,242 @@ +package ca.islandora.syn.valve; + +import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Pattern; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; + +import com.auth0.jwt.algorithms.Algorithm; + +import ca.islandora.syn.settings.SettingsParser; +import ca.islandora.syn.settings.Token; +import ca.islandora.syn.token.InvalidTokenException; +import ca.islandora.syn.token.Verifier; + +/** + * The JWT testing filter + * + * @author jonathangreen + * @author whikloj + * + */ +public class SynFilter implements Filter { + + private static final Logger LOGGER = getLogger(SynFilter.class); + + public static final String UNAUTHORIZED_MSG = "Token authentication failed."; + public static final String FORBIDDEN_MSG = "Token authentication failed."; + + private static String settingsPath; + private static Map algorithmMap; + private static Map staticTokenMap; + private static Map anonymousMap; + + /** + * Constructor + */ + public SynFilter() { + + } + + @Override + public void init(final FilterConfig config) throws ServletException { + settingsPath = config.getInitParameter("settings-path"); + if (settingsPath == null) { + throw new ServletException("settings-path init parameter must have location of syn-settings.yml file."); + } + // Validate the existence of our database file + final File file = new File(settingsPath); + if (!file.exists() || !file.canRead()) { + LOGGER.error("Unable to load Syn configuration from path: " + settingsPath); + throw new ServletException("Unable to load Syn configuration from path: " + settingsPath); + } + + // Load the contents of the database file + try (final FileReader settings = new FileReader(file)) { + final SettingsParser parser = SettingsParser.create(settings); + algorithmMap = parser.getSiteAlgorithms(); + staticTokenMap = parser.getSiteStaticTokens(); + anonymousMap = parser.getSiteAllowAnonymous(); + } catch (final Exception e) { + throw new ServletException("Error parsing Syn configuration", e); + } + } + + @Override + public void destroy() { + } + + @Override + public void doFilter(final ServletRequest servRequest, final ServletResponse servResponse, final FilterChain chain) + throws IOException, ServletException { + final HttpServletRequest request = (HttpServletRequest) servRequest; + final HttpServletResponse response = (HttpServletResponse) servResponse; + + String token = request.getHeader("Authorization"); + if (token == null) { + LOGGER.info("Request did not contain any token."); + // Only check for anonymous access if there is no token. + if ((request.getMethod().equalsIgnoreCase("GET") || + request.getMethod().equals("HEAD")) && + allowGetRequests(buildUrlRegex(request))) { + chain.doFilter(setAnonymousRoles(request), response); + return; + } + response.sendError(SC_UNAUTHORIZED, UNAUTHORIZED_MSG); + return; + } + + final String[] tokenParts = token.split(" "); + if (tokenParts.length != 2 || !tokenParts[0].equalsIgnoreCase("bearer")) { + LOGGER.info("Token was malformed. Token: " + token); + response.sendError(SC_UNAUTHORIZED, UNAUTHORIZED_MSG); + return; + } + + // strip bearer off of the token + token = tokenParts[1]; + + // check if we have a static token that matches + if (staticTokenMap.containsKey(token)) { + LOGGER.info("Site verified using static token."); + chain.doFilter(setUserRolesFromStaticToken(request, staticTokenMap.get(token)), response); + return; + } + + final Verifier verifier; + try { + verifier = Verifier.create(token); + } catch (final InvalidTokenException e) { + response.sendError(SC_UNAUTHORIZED, FORBIDDEN_MSG); + return; + } + + final String url = verifier.getUrl(); + Algorithm algorithm = null; + if (algorithmMap.containsKey(url)) { + algorithm = algorithmMap.get(url); + } else if (algorithmMap.containsKey(null)) { + algorithm = algorithmMap.get(null); + } + + if (algorithm == null) { + LOGGER.info("No key found for site: " + url + "."); + response.sendError(SC_UNAUTHORIZED, UNAUTHORIZED_MSG); + return; + } + + if (verifier.verify(algorithm)) { + LOGGER.info("Site verified: " + url); + chain.doFilter(setUserRolesFromToken(request, verifier), response); + return; + } else { + LOGGER.info("Token failed signature verification: " + url); + response.sendError(SC_FORBIDDEN, FORBIDDEN_MSG); + return; + } + + } + + /** + * Create fake principal with anonymous credentials. + * + * @param request + * The original incoming request. + * @return A wrapper with created credentials. + */ + private HttpServletRequestWrapper setAnonymousRoles(final HttpServletRequest request) { + final String host = request.getScheme() + "://" + request.getServerName() + + (request.getServerPort() != 80 ? ":" + request.getServerPort() : ""); + final List roles = Arrays.asList("anonymous", "islandora", host); + final String name = "anonymous"; + return new SynRequestWrapper(name, roles, request); + } + + /** + * Create fake principal with credentials from static token. + * + * @param request + * The original incoming request. + * @return A wrapper with created credentials. + */ + private HttpServletRequestWrapper setUserRolesFromStaticToken(final HttpServletRequest request, final Token token) { + final List roles = token.getRoles(); + roles.add("islandora"); + final String name = token.getUser(); + return new SynRequestWrapper(name, roles, request); + } + + /** + * Create fake principal with credentials from JWT. + * + * @param request + * The original incoming request. + * @return A wrapper with created credentials. + */ + private HttpServletRequestWrapper setUserRolesFromToken(final HttpServletRequest request, final Verifier verifier) { + final List roles = verifier.getRoles(); + roles.add("islandora"); + roles.add(verifier.getUrl()); + final String name = verifier.getName(); + return new SynRequestWrapper(name, roles, request); + } + + /** + * Do the logic of allowing GET/HEAD requests. + * + * @param hostRegex + * A regular expression built of the hostname. + * @return whether to allow GET requests without authentication. + */ + private boolean allowGetRequests(final String hostRegex) { + // If there is a matching site URI, return its value + for (final Entry site : anonymousMap.entrySet()) { + if (Pattern.matches(hostRegex, site.getKey())) { + return site.getValue(); + } + } + // Else if there is a default, return its value. + if (anonymousMap.containsKey("default")) { + LOGGER.debug( + String.format( + "Using default anonymous ({}) for GET/HEAD requests", + anonymousMap.get("default"))); + return anonymousMap.get("default"); + } + // Else disallow anonymous. + return false; + } + + /** + * Build a regular expression from the current scheme, hostname and port for + * matching against the site urls. + * + * @param request + * The current request. + * @return The regular expression. + */ + private static String buildUrlRegex(final HttpServletRequest request) { + return "^" + request.getScheme() + "://" + Pattern.quote(request.getServerName()) + + "(?::" + request.getServerPort() + ")" + (request.getServerPort() == 80 ? "?" : "") + "/?$"; + } +} diff --git a/src/main/java/ca/islandora/syn/valve/SynRequestWrapper.java b/src/main/java/ca/islandora/syn/valve/SynRequestWrapper.java new file mode 100644 index 0000000..2fbf16b --- /dev/null +++ b/src/main/java/ca/islandora/syn/valve/SynRequestWrapper.java @@ -0,0 +1,52 @@ +package ca.islandora.syn.valve; + +import java.security.Principal; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/** + * Request wrapper to use JWT provided name and roles. + * + * @author whikloj + * @since 2018-01-16 + */ +public class SynRequestWrapper extends HttpServletRequestWrapper { + private final String user; + private List roles; + private final HttpServletRequest realRequest; + + /** + * Constructor + * + * @param user + * The username for the Principal. + * @param roles + * List of roles to include the Principal in. + * @param request + * The original request. + */ + public SynRequestWrapper(final String user, final List roles, final HttpServletRequest request) { + super(request); + this.user = user; + this.realRequest = request; + this.roles = roles; + } + + @Override + public boolean isUserInRole(final String role) { + return roles.contains(role); + } + + @Override + public Principal getUserPrincipal() { + // make an anonymous implementation to just return our user + return new Principal() { + @Override + public String getName() { + return user; + } + }; + } +} diff --git a/src/main/java/ca/islandora/syn/valve/SynValve.java b/src/main/java/ca/islandora/syn/valve/SynValve.java deleted file mode 100644 index 6d0158a..0000000 --- a/src/main/java/ca/islandora/syn/valve/SynValve.java +++ /dev/null @@ -1,213 +0,0 @@ -package ca.islandora.syn.valve; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletResponse; - -import org.apache.catalina.LifecycleException; -import org.apache.catalina.connector.Request; -import org.apache.catalina.connector.Response; -import org.apache.catalina.realm.GenericPrincipal; -import org.apache.catalina.valves.ValveBase; -import org.apache.juli.logging.Log; -import org.apache.juli.logging.LogFactory; -import org.apache.tomcat.util.descriptor.web.SecurityConstraint; - -import com.auth0.jwt.algorithms.Algorithm; - -import ca.islandora.syn.settings.SettingsParser; -import ca.islandora.syn.settings.Token; -import ca.islandora.syn.token.Verifier; - -public class SynValve extends ValveBase { - - private String pathname = "conf/syn-settings.xml"; - private static final Log log = LogFactory.getLog(SynValve.class); - private Map algorithmMap = null; - private Map staticTokenMap = null; - - private Map anonymousGetMap = null; - - @Override - public void invoke(final Request request, final Response response) - throws IOException, ServletException { - - final SecurityConstraint[] constraints = this.container.getRealm() - .findSecurityConstraints(request, request.getContext()); - - if ((constraints == null - && !request.getContext().getPreemptiveAuthentication()) - || !hasAuthConstraint(constraints)) { - this.getNext().invoke(request, response); - } else { - handleAuthentication(request, response); - } - } - - private boolean hasAuthConstraint(final SecurityConstraint[] constraints) { - boolean authConstraint = true; - for (SecurityConstraint securityConstraint : constraints) { - authConstraint &= securityConstraint.getAuthConstraint(); - } - return authConstraint; - } - - private boolean doAuthentication(final Request request) { - String token = request.getHeader("Authorization"); - if (token == null) { - log.info("Request did not contain any token."); - return false; - } - - final String[] tokenParts = token.split(" "); - if (tokenParts.length != 2 || !tokenParts[0].equalsIgnoreCase("bearer")) { - log.info("Token was malformed. Token: " + token); - return false; - } - - // strip bearer off of the token - token = tokenParts[1]; - - // check if we have a static token that matches - if (this.staticTokenMap.containsKey(token)) { - log.info("Site verified using static token."); - setUserRolesFromStaticToken(request, this.staticTokenMap.get(token)); - request.setAuthType("SYN"); - return true; - } - - final Verifier verifier = Verifier.create(token); - if (verifier == null) { - log.info("Token rejected for not containing correct claims."); - return false; - } - - final String url = verifier.getUrl(); - Algorithm algorithm = null; - if (algorithmMap.containsKey(url)) { - algorithm = algorithmMap.get(url); - } else if (algorithmMap.containsKey(null)) { - algorithm = algorithmMap.get(null); - } - - if (algorithm == null) { - log.info("No key found for site: " + url + "."); - return false; - } - - if (verifier.verify(algorithm)) { - log.info("Site verified: " + url); - setUserRolesFromToken(request, verifier); - request.setAuthType("SYN"); - return true; - } else { - log.info("Token failed signature verification: " + url); - return false; - } - } - - private void setAnonymousRoles(final Request request) { - final List roles = new ArrayList(); - roles.add("anonymous"); - roles.add("islandora"); - final String name = "anonymous"; - final GenericPrincipal principal = new GenericPrincipal(name, null, roles); - request.setUserPrincipal(principal); - } - - private void setUserRolesFromStaticToken(final Request request, final Token token) { - final List roles = token.getRoles(); - roles.add("islandora"); - final String name = token.getUser(); - final GenericPrincipal principal = new GenericPrincipal(name, null, roles); - request.setUserPrincipal(principal); - } - - private void setUserRolesFromToken(final Request request, final Verifier verifier) { - final List roles = verifier.getRoles(); - roles.add("islandora"); - roles.add(verifier.getUrl()); - final String name = verifier.getName(); - final GenericPrincipal principal = new GenericPrincipal(name, null, roles); - request.setUserPrincipal(principal); - } - - private void handleAuthentication(final Request request, final Response response) - throws IOException, ServletException { - if ((request.getMethod().equalsIgnoreCase("GET") || - request.getMethod().equals("HEAD")) && - allowGetRequests(request.getHost().toString())) { - // Skip authentication - setAnonymousRoles(request); - this.getNext().invoke(request, response); - } else if (doAuthentication(request)) { - this.getNext().invoke(request, response); - } else { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token authentication failed."); - } - } - - /** - * Do the logic of allowing GET/HEAD requests. - * - * @param requestURI the site being requested - * @return whether to allow GET requests without authentication. - */ - private boolean allowGetRequests(final String requestURI) { - // If there is a matching site URI, return its value - if (anonymousGetMap.containsKey(requestURI)) { - log.debug( - String.format( - "Using site anonymous ({}) for GET/HEAD requests, site {}", - anonymousGetMap.get(requestURI), - requestURI)); - return anonymousGetMap.get(requestURI); - // Else if there is a default, return its value. - } else if (anonymousGetMap.containsKey("default")) { - log.debug( - String.format( - "Using default anonymous ({}) for GET/HEAD requests, host {}", - anonymousGetMap.get("default"), - requestURI)); - return anonymousGetMap.get("default"); - } - // Else disallow anonymous. - return false; - } - - public String getPathname() { - return pathname; - } - public void setPathname(final String pathname) { - this.pathname = pathname; - } - - @Override - public synchronized void startInternal() throws LifecycleException { - // Perform normal superclass initialization - super.startInternal(); - // Validate the existence of our database file - File file = new File(pathname); - if (!file.isAbsolute()) { - file = new File(System.getProperty("catalina.base"), pathname); - } - if (!file.exists() || !file.canRead()) { - throw new LifecycleException("Unable to load XML Configuration from Path: " + pathname); - } - - // Load the contents of the database file - try { - this.algorithmMap = SettingsParser.getSiteAlgorithms(new FileInputStream(file)); - this.staticTokenMap = SettingsParser.getSiteStaticTokens(new FileInputStream(file)); - this.anonymousGetMap = SettingsParser.getSiteAllowAnonymous(new FileInputStream(file)); - } catch (Exception e) { - throw new LifecycleException("Error parsing XML Configuration", e); - } - } -} diff --git a/src/test/java/ca/islandora/syn/settings/SettingsParserAlgorithmsTest.java b/src/test/java/ca/islandora/syn/settings/SettingsParserAlgorithmsTest.java index d33d112..a6f37e5 100644 --- a/src/test/java/ca/islandora/syn/settings/SettingsParserAlgorithmsTest.java +++ b/src/test/java/ca/islandora/syn/settings/SettingsParserAlgorithmsTest.java @@ -1,10 +1,10 @@ package ca.islandora.syn.settings; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.InputStream; +import java.io.StringReader; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Map; @@ -20,19 +20,21 @@ public class SettingsParserAlgorithmsTest { public TemporaryFolder temporaryFolder = new TemporaryFolder(); private void testOneSiteHmacInlineKey(final String algorithm) throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " test data" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(1, algorithms.size()); - assertEquals(true, algorithms.containsKey("http://test.com")); - assertEquals(algorithm, algorithms.get("http://test.com").getName()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: " + algorithm, + " encoding: plain", + " key: test data"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(1, algorithms.size()); + assertEquals(true, algorithms.containsKey("http://test.com")); + assertEquals(algorithm, algorithms.get("http://test.com").getName()); + } } @Test @@ -43,80 +45,109 @@ public void testOneSiteAllHmacInlineKey() throws Exception { } @Test + public void testUnsupportedAlgorithm() throws Exception { + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: AES128", + " encoding: plain", + " key: test data"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + assertFalse(algorithms.containsKey("http://test.com")); + } + } + + @Test(expected = SettingsParserException.class) public void testInvalidSitesVersion() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " test data" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 2", + "site:", + " url: http://test.com", + " algorithm: HS384", + " encoding: plain", + " key: test data"); + + try (final StringReader stream = new StringReader(testYaml)) { + SettingsParser.create(stream).getSiteAlgorithms(); + } } @Test public void testOneSiteHmacBase64() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " am9uYXRoYW4gaXMgYXdlc29tZQ==" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(1, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: HS256", + " encoding: base64", + " key: am9uYXRoYW4gaXMgYXdlc29tZQ=="); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(1, algorithms.size()); + } } @Test public void testOneSiteHmacInvalidBase64() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " this is invalid base64" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: HS256", + " encoding: base64", + " key: this is invalid base64"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + } } @Test public void testOneSiteHmacInvalidEncoding() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " this is invalid base64" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: HS256", + " encoding: badalgorithm", + " key: this is invalid base64"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + } } private void testOneSiteHmacFileKey(final String algorithm) throws Exception { final File key = temporaryFolder.newFile(); final String path = key.getAbsolutePath(); - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(1, algorithms.size()); - assertEquals(true, algorithms.containsKey("http://test.com")); - assertEquals(algorithm, algorithms.get("http://test.com").getName()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: " + algorithm, + " encoding: plain", + " path: " + path); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(1, algorithms.size()); + assertEquals(true, algorithms.containsKey("http://test.com")); + assertEquals(algorithm, algorithms.get("http://test.com").getName()); + } } @Test @@ -128,94 +159,110 @@ public void testOneSiteAllHmacFileKey() throws Exception { @Test public void testSiteBothInlineAndPath() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " test data" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: HS384", + " encoding: plain", + " path: foo", + " key: test data"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + } } @Test public void testSiteNeitherInlineAndPath() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: HS384", + " encoding: plain"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + } } @Test public void testSiteInvalidPath() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: HS384", + " encoding: plain", + " path: foo"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + } } @Test public void testSiteNoUrlDefault() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " test data" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(1, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " algorithm: HS256", + " encoding: plain", + " default: true", + " key: test data"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(1, algorithms.size()); + } } @Test public void testSiteNoUrl() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " test data" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " algorithm: HS256", + " encoding: plain", + " key: test data"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + } } private void testOneSiteRsaInlineKey(final String algorithm) throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "-----BEGIN PUBLIC KEY-----" - , "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVO4MNlZG+iGYhoJd/cBpfMd9" - , "YnKsntF+zhQs8lCbBabgY8kNoXVIEeOm4WPJ+W53gLDAIg6BNrZqxk9z1TLD6Dmz" - , "t176OLYkNoTI9LNf6z4wuBenrlQ/H5UnYl6h5QoOdVpNAgEjkDcdTSOE1lqFLIle" - , "KOT4nEF7MBGyOSP3KQIDAQAB" - , "-----END PUBLIC KEY-----" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(1, algorithms.size()); - assertEquals(true, algorithms.containsKey("http://test.com")); - assertEquals(algorithm, algorithms.get("http://test.com").getName()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: " + algorithm, + " encoding: PEM", + " key: |", + " -----BEGIN PUBLIC KEY-----", + " MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVO4MNlZG+iGYhoJd/cBpfMd9", + " YnKsntF+zhQs8lCbBabgY8kNoXVIEeOm4WPJ+W53gLDAIg6BNrZqxk9z1TLD6Dmz", + " t176OLYkNoTI9LNf6z4wuBenrlQ/H5UnYl6h5QoOdVpNAgEjkDcdTSOE1lqFLIle", + " KOT4nEF7MBGyOSP3KQIDAQAB", + " -----END PUBLIC KEY-----"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(1, algorithms.size()); + assertEquals(true, algorithms.containsKey("http://test.com")); + assertEquals(algorithm, algorithms.get("http://test.com").getName()); + } } @Test @@ -229,28 +276,29 @@ private void testOneSiteRsaFileKey(final String algorithm) throws Exception { final File keyFile = temporaryFolder.newFile(); final String path = keyFile.getAbsolutePath(); - final String pemPublicKey = String.join("\n" - , "-----BEGIN PUBLIC KEY-----" - , "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVO4MNlZG+iGYhoJd/cBpfMd9" - , "YnKsntF+zhQs8lCbBabgY8kNoXVIEeOm4WPJ+W53gLDAIg6BNrZqxk9z1TLD6Dmz" - , "t176OLYkNoTI9LNf6z4wuBenrlQ/H5UnYl6h5QoOdVpNAgEjkDcdTSOE1lqFLIle" - , "KOT4nEF7MBGyOSP3KQIDAQAB" - , "-----END PUBLIC KEY-----" - ); + final String pemPublicKey = String.join("\n", "-----BEGIN PUBLIC KEY-----", + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVO4MNlZG+iGYhoJd/cBpfMd9", + "YnKsntF+zhQs8lCbBabgY8kNoXVIEeOm4WPJ+W53gLDAIg6BNrZqxk9z1TLD6Dmz", + "t176OLYkNoTI9LNf6z4wuBenrlQ/H5UnYl6h5QoOdVpNAgEjkDcdTSOE1lqFLIle", "KOT4nEF7MBGyOSP3KQIDAQAB", + "-----END PUBLIC KEY-----"); Files.write(Paths.get(path), pemPublicKey.getBytes()); - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(1, algorithms.size()); - assertEquals(true, algorithms.containsKey("http://test.com")); - assertEquals(algorithm, algorithms.get("http://test.com").getName()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: " + algorithm, + " encoding: PEM", + " path: " + path); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(1, algorithms.size()); + assertEquals(true, algorithms.containsKey("http://test.com")); + assertEquals(algorithm, algorithms.get("http://test.com").getName()); + } } @Test @@ -262,45 +310,52 @@ public void testOneSiteAllRsaFileKey() throws Exception { @Test public void testOneSiteAllRsaInvalidEncoding() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "-----BEGIN PUBLIC KEY-----" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS256", + " encoding: PEM", + " key: |", + " -----BEGIN PUBLIC KEY-----"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + } } - @Test public void testMultipleDefaults() throws Exception { final File keyFile = temporaryFolder.newFile(); final String path = keyFile.getAbsolutePath(); - final String pemPublicKey = String.join("\n" - , "-----BEGIN PUBLIC KEY-----" - , "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVO4MNlZG+iGYhoJd/cBpfMd9" - , "YnKsntF+zhQs8lCbBabgY8kNoXVIEeOm4WPJ+W53gLDAIg6BNrZqxk9z1TLD6Dmz" - , "t176OLYkNoTI9LNf6z4wuBenrlQ/H5UnYl6h5QoOdVpNAgEjkDcdTSOE1lqFLIle" - , "KOT4nEF7MBGyOSP3KQIDAQAB" - , "-----END PUBLIC KEY-----" - ); + final String pemPublicKey = String.join("\n", "-----BEGIN PUBLIC KEY-----", + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVO4MNlZG+iGYhoJd/cBpfMd9", + "YnKsntF+zhQs8lCbBabgY8kNoXVIEeOm4WPJ+W53gLDAIg6BNrZqxk9z1TLD6Dmz", + "t176OLYkNoTI9LNf6z4wuBenrlQ/H5UnYl6h5QoOdVpNAgEjkDcdTSOE1lqFLIle", "KOT4nEF7MBGyOSP3KQIDAQAB", + "-----END PUBLIC KEY-----"); Files.write(Paths.get(path), pemPublicKey.getBytes()); - final String testXml = String.join("\n" - , "" - , " " - , " " - , "" - ); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(1, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " algorithm: RS384", + " path: " + path, + " encoding: PEM", + " default: true", + "site:", + " algorithm: HS256", + " path: " + path, + " encoding: plain", + " default: true"); + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(1, algorithms.size()); + } } @Test @@ -308,24 +363,25 @@ public void testInvalidAlgorithm() throws Exception { final File keyFile = temporaryFolder.newFile(); final String path = keyFile.getAbsolutePath(); - final String pemPublicKey = String.join("\n" - , "-----BEGIN PUBLIC KEY-----" - , "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVO4MNlZG+iGYhoJd/cBpfMd9" - , "YnKsntF+zhQs8lCbBabgY8kNoXVIEeOm4WPJ+W53gLDAIg6BNrZqxk9z1TLD6Dmz" - , "t176OLYkNoTI9LNf6z4wuBenrlQ/H5UnYl6h5QoOdVpNAgEjkDcdTSOE1lqFLIle" - , "KOT4nEF7MBGyOSP3KQIDAQAB" - , "-----END PUBLIC KEY-----" - ); + final String pemPublicKey = String.join("\n", "-----BEGIN PUBLIC KEY-----", + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEVO4MNlZG+iGYhoJd/cBpfMd9", + "YnKsntF+zhQs8lCbBabgY8kNoXVIEeOm4WPJ+W53gLDAIg6BNrZqxk9z1TLD6Dmz", + "t176OLYkNoTI9LNf6z4wuBenrlQ/H5UnYl6h5QoOdVpNAgEjkDcdTSOE1lqFLIle", "KOT4nEF7MBGyOSP3KQIDAQAB", + "-----END PUBLIC KEY-----"); Files.write(Paths.get(path), pemPublicKey.getBytes()); - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map algorithms = SettingsParser.getSiteAlgorithms(stream); - assertEquals(0, algorithms.size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " algorithm: RSA384", + " path: " + path, + " encoding: PEM", + " default: true"); + try (final StringReader stream = new StringReader(testYaml)) { + final Map algorithms = SettingsParser.create(stream).getSiteAlgorithms(); + assertEquals(0, algorithms.size()); + } } } diff --git a/src/test/java/ca/islandora/syn/settings/SettingsParserAnonymousTest.java b/src/test/java/ca/islandora/syn/settings/SettingsParserAnonymousTest.java index 9c7d321..917beb0 100644 --- a/src/test/java/ca/islandora/syn/settings/SettingsParserAnonymousTest.java +++ b/src/test/java/ca/islandora/syn/settings/SettingsParserAnonymousTest.java @@ -2,8 +2,7 @@ import static org.junit.Assert.assertEquals; -import java.io.ByteArrayInputStream; -import java.io.InputStream; +import java.io.StringReader; import java.util.Map; import org.junit.Test; @@ -12,65 +11,84 @@ public class SettingsParserAnonymousTest { @Test public void testSiteAnonymousOn() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " test data" - , " " - , "" - ); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS256", + " encoding: plain", + " anonymous: true", + " key: test data"); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map anonymous = SettingsParser.getSiteAllowAnonymous(stream); - assertEquals(1, anonymous.size()); - assertEquals(true, anonymous.containsKey("http://test.com")); - assertEquals(true, anonymous.get("http://test.com")); + try (final StringReader stream = new StringReader(testYaml)) { + final Map anonymous = SettingsParser.create(stream).getSiteAllowAnonymous(); + assertEquals(1, anonymous.size()); + assertEquals(true, anonymous.containsKey("http://test.com")); + assertEquals(true, anonymous.get("http://test.com")); + } } @Test public void testSiteMultipleAnonymousTest() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " test data" - , " " - , " " - , " test data" - , " " - , "" - ); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS256", + " encoding: plain", + " anonymous: true", + " key: test data", + "site:", + " url: http://test2.com", + " algorithm: RS256", + " encoding: plain", + " anonymous: false", + " key: test data"); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map anonymous = SettingsParser.getSiteAllowAnonymous(stream); - assertEquals(2, anonymous.size()); - assertEquals(true, anonymous.containsKey("http://test.com")); - assertEquals(true, anonymous.get("http://test.com")); - assertEquals(true, anonymous.containsKey("http://test2.com")); - assertEquals(false, anonymous.get("http://test2.com")); + try (final StringReader stream = new StringReader(testYaml)) { + final Map anonymous = SettingsParser.create(stream).getSiteAllowAnonymous(); + assertEquals(2, anonymous.size()); + assertEquals(true, anonymous.containsKey("http://test.com")); + assertEquals(true, anonymous.get("http://test.com")); + assertEquals(true, anonymous.containsKey("http://test2.com")); + assertEquals(false, anonymous.get("http://test2.com")); + } } @Test public void testDefaultMultipleAnonymousTest() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " test data" - , " " - , " " - , " test data" - , " " - , " " - , "" - ); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS256", + " encoding: plain", + " anonymous: true", + " key: test data", + "site:", + " url: http://test2.com", + " algorithm: RS256", + " encoding: plain", + " anonymous: false", + " key: test data", + "site:", + " algorithm: RS256", + " encoding: plain", + " anonymous: true", + " default: true"); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map anonymous = SettingsParser.getSiteAllowAnonymous(stream); - assertEquals(3, anonymous.size()); - assertEquals(true, anonymous.containsKey("http://test.com")); - assertEquals(true, anonymous.get("http://test.com")); - assertEquals(true, anonymous.containsKey("http://test2.com")); - assertEquals(false, anonymous.get("http://test2.com")); - assertEquals(true, anonymous.containsKey("default")); - assertEquals(true, anonymous.get("default")); + try (final StringReader stream = new StringReader(testYaml)) { + final Map anonymous = SettingsParser.create(stream).getSiteAllowAnonymous(); + assertEquals(3, anonymous.size()); + assertEquals(true, anonymous.containsKey("http://test.com")); + assertEquals(true, anonymous.get("http://test.com")); + assertEquals(true, anonymous.containsKey("http://test2.com")); + assertEquals(false, anonymous.get("http://test2.com")); + assertEquals(true, anonymous.containsKey("default")); + assertEquals(true, anonymous.get("default")); + } } } diff --git a/src/test/java/ca/islandora/syn/settings/SettingsParserDigestTest.java b/src/test/java/ca/islandora/syn/settings/SettingsParserDigestTest.java index 77daec8..cfc3486 100644 --- a/src/test/java/ca/islandora/syn/settings/SettingsParserDigestTest.java +++ b/src/test/java/ca/islandora/syn/settings/SettingsParserDigestTest.java @@ -5,8 +5,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import java.io.ByteArrayInputStream; -import java.io.InputStream; +import java.io.StringReader; import org.junit.Test; @@ -14,127 +13,141 @@ public class SettingsParserDigestTest { @Test public void testOneSitePath() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Config settings = SettingsParser.getSitesObject(stream); - assertEquals(12, settings.getVersion()); - assertEquals(1, settings.getSites().size()); - - final Site site = settings.getSites().get(0); - assertEquals("RS384", site.getAlgorithm()); - assertEquals("http://test.com", site.getUrl()); - assertEquals("test/path.key", site.getPath()); - assertEquals("PEM", site.getEncoding()); - assertEquals("", site.getKey()); - assertFalse(site.getDefault()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS384", + " path: test/path.key", + " encoding: PEM"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Config settings = SettingsParser.create(stream).getConfig(); + assertEquals(1, settings.getVersion()); + assertEquals(1, settings.getSites().size()); + + final Site site = settings.getSites().get(0); + assertEquals("RS384", site.getAlgorithm()); + assertEquals("http://test.com", site.getUrl()); + assertEquals("test/path.key", site.getPath()); + assertEquals("PEM", site.getEncoding()); + assertNull(site.getKey()); + assertFalse(site.getDefault()); + } } @Test public void testOneSiteKey() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "multiline" - , "key" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Config settings = SettingsParser.getSitesObject(stream); - assertEquals(-1, settings.getVersion()); - assertEquals(1, settings.getSites().size()); - - final Site site = settings.getSites().get(0); - assertEquals("RS384", site.getAlgorithm()); - assertEquals("http://test.com", site.getUrl()); - assertNull(site.getPath()); - assertEquals("PEM", site.getEncoding()); - assertEquals("multiline\nkey", site.getKey()); - assertTrue(site.getDefault()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS384", + " encoding: PEM", + " default: true", + " key: |", + " multiline", + " key"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Config settings = SettingsParser.create(stream).getConfig(); + assertEquals(1, settings.getVersion()); + assertEquals(1, settings.getSites().size()); + + final Site site = settings.getSites().get(0); + assertEquals("RS384", site.getAlgorithm()); + assertEquals("http://test.com", site.getUrl()); + assertNull(site.getPath()); + assertEquals("PEM", site.getEncoding()); + assertEquals("multiline\nkey", site.getKey()); + assertTrue(site.getDefault()); + } } @Test public void testTwoSites() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Config settings = SettingsParser.getSitesObject(stream); - assertEquals(2, settings.getSites().size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + "site:"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Config settings = SettingsParser.create(stream).getConfig(); + assertEquals(2, settings.getSites().size()); + } } - @Test + @Test(expected = SettingsParserException.class) public void testOneSiteUnexpectedAttribute() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Config settings = SettingsParser.getSitesObject(stream); - assertEquals(1, settings.getSites().size()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " unexpected: woh"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Config settings = SettingsParser.create(stream).getConfig(); + assertEquals(1, settings.getSites().size()); + } } - @Test - public void testOneSiteUnexpectedTag() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Config settings = SettingsParser.getSitesObject(stream); - assertEquals(0, settings.getSites().size()) -; } - @Test public void testValidAnonymousTrue() throws Exception { - final String testXml = "\n" + - " \n" + - ""; - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Config settings = SettingsParser.getSitesObject(stream); - final Site sites = settings.getSites().get(0); - assertTrue("Did not set anonymous property", sites.getAnonymous()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS384", + " encoding: PEM", + " default: true", + " anonymous: true"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Config settings = SettingsParser.create(stream).getConfig(); + final Site sites = settings.getSites().get(0); + assertTrue("Did not set anonymous property", sites.getAnonymous()); + } } @Test public void testValidAnonymousFalse() throws Exception { - final String testXml = "\n" + - " \n" + - ""; - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Config settings = SettingsParser.getSitesObject(stream); - final Site sites = settings.getSites().get(0); - assertFalse("Did not set anonymous property", sites.getAnonymous()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS384", + " encoding: PEM", + " default: true", + " anonymous: false"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Config settings = SettingsParser.create(stream).getConfig(); + final Site sites = settings.getSites().get(0); + assertFalse("Did not set anonymous property", sites.getAnonymous()); + } } - @Test + @Test(expected = SettingsParserException.class) public void testInvalidAnonymous() throws Exception { - final String testXml = "\n" + - " \n" + - ""; - - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Config settings = SettingsParser.getSitesObject(stream); - final Site sites = settings.getSites().get(0); - assertFalse("Did not set anonymous property", sites.getAnonymous()); + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: RS384", + " encoding: PEM", + " default: true", + " anonymous: whatever"); + + try (final StringReader stream = new StringReader(testYaml)) { + final Config settings = SettingsParser.create(stream).getConfig(); + final Site sites = settings.getSites().get(0); + assertFalse("Did not set anonymous property", sites.getAnonymous()); + } } } diff --git a/src/test/java/ca/islandora/syn/settings/SettingsParserTokenTest.java b/src/test/java/ca/islandora/syn/settings/SettingsParserTokenTest.java index 85f1513..3c75e6a 100644 --- a/src/test/java/ca/islandora/syn/settings/SettingsParserTokenTest.java +++ b/src/test/java/ca/islandora/syn/settings/SettingsParserTokenTest.java @@ -1,82 +1,79 @@ package ca.islandora.syn.settings; -import org.junit.Test; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.Map; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.StringReader; +import java.util.Map; + +import org.junit.Test; + public class SettingsParserTokenTest { - @Test + @Test(expected = SettingsParserException.class) public void testInvalidVersion() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " c00lpazzward" - , " " - , "" - ); + final String testYaml = String.join("\n", + "---", + "version: 2", + "token:", + " value: c00lpazzward"); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map tokens = SettingsParser.getSiteStaticTokens(stream); - assertEquals(0, tokens.size()); + try (final StringReader stream = new StringReader(testYaml)) { + SettingsParser.create(stream).getSiteStaticTokens(); + } } @Test public void testTokenNoParams() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " c00lpazzward" - , " " - , "" - ); + final String testYaml = String.join("\n", + "---", + "version: 1", + "token:", + " value: c00lpazzward"); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map tokens = SettingsParser.getSiteStaticTokens(stream); - final Token token = tokens.get("c00lpazzward"); - assertEquals(1, tokens.size()); - assertEquals("c00lpazzward", token.getToken()); - assertEquals("islandoraAdmin", token.getUser()); - assertEquals(0, token.getRoles().size()); + try (final StringReader stream = new StringReader(testYaml)) { + final Map tokens = SettingsParser.create(stream).getSiteStaticTokens(); + final Token token = tokens.get("c00lpazzward"); + assertEquals(1, tokens.size()); + assertEquals("c00lpazzward", token.getValue()); + assertEquals("islandoraAdmin", token.getUser()); + assertEquals(0, token.getRoles().size()); + } } @Test public void testTokenUser() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " c00lpazzward" - , " " - , "" - ); + final String testYaml = String.join("\n", + "---", + "version: 1", + "token:", + " user: denis", + " value: c00lpazzward"); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map tokens = SettingsParser.getSiteStaticTokens(stream); - final Token token = tokens.get("c00lpazzward"); - assertEquals(1, tokens.size()); - assertEquals("denis", token.getUser()); + try (final StringReader stream = new StringReader(testYaml)) { + final Map tokens = SettingsParser.create(stream).getSiteStaticTokens(); + final Token token = tokens.get("c00lpazzward"); + assertEquals(1, tokens.size()); + assertEquals("denis", token.getUser()); + } } @Test public void testTokenRole() throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , " c00lpazzward" - , " " - , "" - ); + final String testYaml = String.join("\n", + "---", + "version: 1", + "token:", + " roles: role1,role2,role3", + " value: c00lpazzward"); - final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); - final Map tokens = SettingsParser.getSiteStaticTokens(stream); - final Token token = tokens.get("c00lpazzward"); - assertEquals(1, tokens.size()); - assertEquals(3, token.getRoles().size()); - assertTrue(token.getRoles().contains("role1")); - assertTrue(token.getRoles().contains("role2")); - assertTrue(token.getRoles().contains("role3")); + try (final StringReader stream = new StringReader(testYaml)) { + final Map tokens = SettingsParser.create(stream).getSiteStaticTokens(); + final Token token = tokens.get("c00lpazzward"); + assertEquals(1, tokens.size()); + assertEquals(3, token.getRoles().size()); + assertTrue(token.getRoles().contains("role1")); + assertTrue(token.getRoles().contains("role2")); + assertTrue(token.getRoles().contains("role3")); + } } } diff --git a/src/test/java/ca/islandora/syn/settings/SitesTest.java b/src/test/java/ca/islandora/syn/settings/SitesTest.java index 22ca6da..e102e06 100644 --- a/src/test/java/ca/islandora/syn/settings/SitesTest.java +++ b/src/test/java/ca/islandora/syn/settings/SitesTest.java @@ -1,8 +1,9 @@ package ca.islandora.syn.settings; -import org.junit.Test; import static org.junit.Assert.assertEquals; +import org.junit.Test; + public class SitesTest { @Test @@ -10,7 +11,7 @@ public void TestJwtSites() { final Config sites = new Config(); assertEquals(-1, sites.getVersion()); sites.setVersion(2); - assertEquals(2,sites.getVersion()); + assertEquals(2, sites.getVersion()); assertEquals(0, sites.getSites().size()); final Site site = new Site(); diff --git a/src/test/java/ca/islandora/syn/settings/TokenTest.java b/src/test/java/ca/islandora/syn/settings/TokenTest.java index 5f6647a..53c3604 100644 --- a/src/test/java/ca/islandora/syn/settings/TokenTest.java +++ b/src/test/java/ca/islandora/syn/settings/TokenTest.java @@ -1,11 +1,11 @@ package ca.islandora.syn.settings; -import org.junit.Before; -import org.junit.Test; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; + public class TokenTest { Token token; @@ -40,11 +40,11 @@ public void testTokenRoles() { @Test public void testTokenToken() { - assertTrue(this.token.getToken().isEmpty()); + assertTrue(this.token.getValue().isEmpty()); final String testVal = "test"; - this.token.setToken(testVal); - assertEquals(testVal, this.token.getToken()); - this.token.setToken(" " + testVal); - assertEquals(testVal, this.token.getToken()); + this.token.setValue(testVal); + assertEquals(testVal, this.token.getValue()); + this.token.setValue(" " + testVal); + assertEquals(testVal, this.token.getValue()); } } diff --git a/src/test/java/ca/islandora/syn/token/VerifierTest.java b/src/test/java/ca/islandora/syn/token/VerifierTest.java index 9933556..7d519b7 100644 --- a/src/test/java/ca/islandora/syn/token/VerifierTest.java +++ b/src/test/java/ca/islandora/syn/token/VerifierTest.java @@ -35,13 +35,14 @@ public void setUp() { @Test public void testClaimsWithoutVerify() { token = JWT.create() - .withArrayClaim("roles", new String[]{"Role1", "Role2"}) - .withClaim("uid", 1) - .withClaim("name", "admin") - .withClaim("url", "http://test.com") + .withArrayClaim("roles", new String[] { "Role1", "Role2" }) + .withClaim("webid", 1) + .withClaim("sub", "admin") + .withClaim("iss", "http://test.com") .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) .sign(Algorithm.none()); + final Verifier verifier = Verifier.create(token); assertEquals(1, verifier.getUid()); assertEquals("admin", verifier.getName()); @@ -52,17 +53,16 @@ public void testClaimsWithoutVerify() { assertEquals("Role2", roles.get(1)); } - @Test + @Test(expected = InvalidTokenException.class) public void testClaimsMissing() { token = JWT.create() - .withClaim("name", "admin") - .withClaim("url", "http://test.com") + .withClaim("sub", "admin") + .withClaim("iss", "http://test.com") .sign(Algorithm.none()); - final Verifier verifier = Verifier.create(token); - assertNull(verifier); + Verifier.create(token); } - @Test + @Test(expected = InvalidTokenException.class) public void testClaimsBad() { token = "gibberish"; final Verifier verifier = Verifier.create(token); @@ -72,10 +72,10 @@ public void testClaimsBad() { @Test public void testClaimsAndVerifyHmac() throws Exception { token = JWT.create() - .withArrayClaim("roles", new String[]{"Role1", "Role2"}) - .withClaim("uid", 1) - .withClaim("name", "admin") - .withClaim("url", "http://test.com") + .withArrayClaim("roles", new String[] { "Role1", "Role2" }) + .withClaim("webid", 1) + .withClaim("sub", "admin") + .withClaim("iss", "http://test.com") .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) .sign(Algorithm.HMAC256("secret")); @@ -101,10 +101,10 @@ public void testClaimsAndVerifyRsa() throws Exception { final RSAKey publicKey = (RSAKey) pair.getPublic(); token = JWT.create() - .withArrayClaim("roles", new String[]{"Role1", "Role2"}) - .withClaim("uid", 1) - .withClaim("name", "admin") - .withClaim("url", "http://test.com") + .withArrayClaim("roles", new String[] { "Role1", "Role2" }) + .withClaim("webid", 1) + .withClaim("sub", "admin") + .withClaim("iss", "http://test.com") .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) .sign(Algorithm.RSA512(privateKey)); @@ -125,10 +125,10 @@ public void testClaimsAndVerifyRsa() throws Exception { @Test public void testClaimsAndVerifyHmacBadIssueDate() throws Exception { token = JWT.create() - .withArrayClaim("roles", new String[]{"Role1", "Role2"}) - .withClaim("uid", 1) - .withClaim("name", "admin") - .withClaim("url", "http://test.com") + .withArrayClaim("roles", new String[] { "Role1", "Role2" }) + .withClaim("webid", 1) + .withClaim("sub", "admin") + .withClaim("iss", "http://test.com") .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) .withExpiresAt(Date.from(LocalDateTime.now().minusHours(2).toInstant(offset))) .sign(Algorithm.HMAC256("secret")); diff --git a/src/test/java/ca/islandora/syn/valves/SynFilterTest.java b/src/test/java/ca/islandora/syn/valves/SynFilterTest.java new file mode 100644 index 0000000..52db131 --- /dev/null +++ b/src/test/java/ca/islandora/syn/valves/SynFilterTest.java @@ -0,0 +1,623 @@ +package ca.islandora.syn.valves; + +import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; +import static junit.framework.TestCase.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; + +import ca.islandora.syn.valve.SynFilter; + +@RunWith(MockitoJUnitRunner.class) +public class SynFilterTest { + + private SynFilter synFilter; + + private File settings; + + @Mock + private FilterChain chain; + + @Mock + private FilterConfig config; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private static ZoneOffset offset; + + private ArgumentCaptor requestCaptor; + private ArgumentCaptor responseCaptor; + + @Before + public void setUp() throws Exception { + settings = temporaryFolder.newFile(); + createSettings(settings); + + when(config.getInitParameter("settings-path")).thenReturn(settings.getAbsolutePath()); + + synFilter = createFilter(); + + when(request.getScheme()).thenReturn("http"); + when(request.getServerPort()).thenReturn(80); + + offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()); + + requestCaptor = ArgumentCaptor.forClass(HttpServletRequest.class); + responseCaptor = ArgumentCaptor.forClass(HttpServletResponse.class); + } + + private SynFilter createFilter() throws ServletException { + final SynFilter synFilter = new SynFilter(); + synFilter.init(config); + return synFilter; + } + + @Test(expected = ServletException.class) + public void missingInitParameter() throws Exception { + when(config.getInitParameter("settings-path")).thenReturn(null); + synFilter = createFilter(); + } + + @Test(expected = ServletException.class) + public void absoluteSettingsDoesNotExist() throws Exception { + when(config.getInitParameter("settings-path")).thenReturn("/tmp/fileIsFake"); + synFilter = createFilter(); + } + + @Test(expected = ServletException.class) + public void relativeSettingsDoesNotExist() throws Exception { + when(config.getInitParameter("settings-path")).thenReturn("fileIsFake"); + synFilter = createFilter(); + } + + @Test(expected = ServletException.class) + public void settingsParseFail() throws Exception { + final String testYaml = String.join("\n", + "---", + "version: bad", + "site:", + " url: http://test.com", + " algorithm: HS256", + " encoding: plain", + " anonymous: false", + " key: secret"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + } + + @Test + public void shouldPassAuth() throws Exception { + final String host = "http://test.com"; + final String username = "adminuser"; + final String[] roles = new String[] { "role1", "role2", "role3" }; + final ArrayList finalRoles = new ArrayList(Arrays.asList(roles)); + finalRoles.add("islandora"); + finalRoles.add(host); + + final String token = "Bearer " + JWT + .create() + .withClaim("webid", 1) + .withClaim("sub", username) + .withClaim("iss", host) + .withArrayClaim("roles", roles) + .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) + .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) + .sign(Algorithm.HMAC256("secret")); + + when(request.getHeader("Authorization")).thenReturn(token); + + synFilter.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + assertEquals(username, requestCaptor.getValue().getUserPrincipal().getName()); + + finalRoles.forEach(role -> assertTrue(requestCaptor.getValue().isUserInRole(role))); + + } + + @Test + public void shouldPassAuthToken() throws Exception { + final String defaultUser = "islandoraAdmin"; + final String token = "Bearer 1337"; + + when(request.getHeader("Authorization")) + .thenReturn(token); + + synFilter.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + assertEquals(defaultUser, requestCaptor.getValue().getUserPrincipal().getName()); + assertTrue(requestCaptor.getValue().isUserInRole("islandora")); + } + + @Test + public void shouldFailAuthBecauseOfTokenNotSet() throws Exception { + when(request.getMethod()).thenReturn("GET"); + when(request.getServerName()).thenReturn("test.com"); + + synFilter.doFilter(request, response, chain); + + verify(request).getHeader("Authorization"); + verify(response).sendError(SC_UNAUTHORIZED, SynFilter.UNAUTHORIZED_MSG); + } + + @Test + public void shouldFailAuthBecauseOfTokenInvalid1() throws Exception { + when(request.getHeader("Authorization")) + .thenReturn("garbage"); + + synFilter.doFilter(request, response, chain); + + verify(request).getHeader("Authorization"); + verify(response).sendError(SC_UNAUTHORIZED, SynFilter.UNAUTHORIZED_MSG); + } + + @Test + public void shouldFailAuthBecauseOfTokenInvalid2() throws Exception { + when(request.getHeader("Authorization")) + .thenReturn("killer bandit foo"); + + synFilter.doFilter(request, response, chain); + + verify(request).getHeader("Authorization"); + verify(response).sendError(SC_UNAUTHORIZED, SynFilter.UNAUTHORIZED_MSG); + } + + @Test + public void shouldFailTokenMissingUid() throws Exception { + final String host = "http://test.com"; + final String username = "adminuser"; + final String[] roles = new String[] { "role1", "role2", "role3" }; + final ArrayList finalRoles = new ArrayList(Arrays.asList(roles)); + finalRoles.add("islandora"); + finalRoles.add(host); + + final String token = JWT + .create() + .withClaim("sub", username) + .withClaim("iss", host) + .withArrayClaim("roles", roles) + .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) + .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) + .sign(Algorithm.HMAC256("secret")); + + when(request.getHeader("Authorization")) + .thenReturn("Bearer " + token); + + synFilter.doFilter(request, response, chain); + + verify(request).getHeader("Authorization"); + verify(response).sendError(SC_UNAUTHORIZED, SynFilter.UNAUTHORIZED_MSG); + } + + @Test + public void shouldPassAuthDefaultSite() throws Exception { + final String host = "http://test2.com"; + final String username = "normalUser"; + final List finalRoles = Arrays.asList("islandora", host); + + final String token = JWT + .create() + .withClaim("webid", 1) + .withClaim("sub", username) + .withClaim("iss", host) + .withArrayClaim("roles", new String[] {}) + .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) + .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) + .sign(Algorithm.HMAC256("secret2")); + + when(request.getHeader("Authorization")) + .thenReturn("Bearer " + token); + + synFilter.doFilter(request, response, chain); + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + verify(request).getHeader("Authorization"); + assertEquals(username, requestCaptor.getValue().getUserPrincipal().getName()); + + finalRoles.forEach(role -> assertTrue(requestCaptor.getValue().isUserInRole(role))); + } + + @Test + public void shouldFailAuthBecauseNoSiteMatch() throws Exception { + final String host = "http://test-no-match.com"; + final String username = "normalUser"; + final String token = JWT + .create() + .withClaim("webid", 1) + .withClaim("sub", username) + .withClaim("iss", host) + .withArrayClaim("roles", new String[] {}) + .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) + .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) + .sign(Algorithm.HMAC256("secret")); + + when(request.getHeader("Authorization")) + .thenReturn("Bearer " + token); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: HS256", + " encoding: plain", + " anonymous: false", + " key: secret"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + // Recreate because the settings file has changed so we need to run init again. + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(request).getHeader("Authorization"); + verify(response).sendError(SC_UNAUTHORIZED, SynFilter.UNAUTHORIZED_MSG); + } + + @Test + public void allowAuthWithToken() throws Exception { + final String host = "http://anon-test.com"; + final String username = "Bob"; + final List finalRoles = Arrays.asList("islandora", host); + final String token = JWT + .create() + .withClaim("webid", 1) + .withClaim("sub", username) + .withClaim("iss", host) + .withArrayClaim("roles", new String[] {}) + .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) + .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) + .sign(Algorithm.HMAC256("secretFool")); + + when(request.getHeader("Authorization")) + .thenReturn("Bearer " + token); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: " + host, + " algorithm: HS256", + " encoding: plain", + " anonymous: false", + " key: secretFool"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + + synFilter.doFilter(request, response, chain); + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + assertEquals(username, requestCaptor.getValue().getUserPrincipal().getName()); + verify(request).getHeader("Authorization"); + + finalRoles.forEach(role -> assertTrue(requestCaptor.getValue().isUserInRole(role))); + } + + @Test + public void allowGetWithoutToken() throws Exception { + final String servername = "anon-test.com"; + final String host = "http://" + servername; + final String username = "anonymous"; + final List finalRoles = Arrays.asList("islandora", "anonymous", host); + + when(request.getMethod()).thenReturn("GET"); + when(request.getServerName()).thenReturn(servername); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: " + host, + " algorithm: HS256", + " encoding: plain", + " anonymous: true", + " key: secretFool"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + assertEquals(username, requestCaptor.getValue().getUserPrincipal().getName()); + finalRoles.forEach(role -> assertTrue(requestCaptor.getValue().isUserInRole(role))); + } + + @Test + public void allowHeadWithoutToken() throws Exception { + final String servername = "anon-test.com"; + final String host = "http://" + servername; + final String username = "anonymous"; + final List finalRoles = Arrays.asList("islandora", "anonymous", host); + + when(request.getMethod()).thenReturn("HEAD"); + when(request.getServerName()).thenReturn(servername); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: " + host, + " algorithm: HS256", + " encoding: plain", + " anonymous: true", + " key: secretFool"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + assertEquals(username, requestCaptor.getValue().getUserPrincipal().getName()); + finalRoles.forEach(role -> assertTrue(requestCaptor.getValue().isUserInRole(role))); + } + + @Test + public void disallowGetWithoutToken() throws Exception { + final String servername = "anon-test.com"; + final String nohost = "http://other-site.com"; + + when(request.getMethod()).thenReturn("GET"); + when(request.getServerName()).thenReturn(servername); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: " + nohost, + " algorithm: HS256", + " encoding: plain", + " key: secretFool"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(request).getHeader("Authorization"); + verify(response).sendError(SC_UNAUTHORIZED, SynFilter.UNAUTHORIZED_MSG); + } + + @Test + public void overrideDefaultAllow() throws Exception { + final String servername = "anon-test.com"; + final String host = "http://" + servername; + final String username = "normalUser123"; + final List finalRoles = Arrays.asList("islandora", host); + final String token = JWT + .create() + .withClaim("webid", 1) + .withClaim("sub", username) + .withClaim("iss", host) + .withArrayClaim("roles", new String[] {}) + .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) + .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) + .sign(Algorithm.HMAC256("secretFool")); + + when(request.getHeader("Authorization")) + .thenReturn("Bearer " + token); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: " + host, + " algorithm: HS256", + " encoding: plain", + " anonymous: false", + " key: secretFool", + "site:", + " algorithm: RS256", + " encoding: plain", + " anonymous: true", + " default: true"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + assertEquals(username, requestCaptor.getValue().getUserPrincipal().getName()); + finalRoles.forEach(role -> assertTrue(requestCaptor.getValue().isUserInRole(role))); + } + + @Test + public void overrideDefaultAllowAndFail() throws Exception { + final String servername = "anon-test.com"; + final String host = "http://" + servername; + final String username = "normalUser123"; + final String token = JWT + .create() + .withClaim("webid", 1) + .withClaim("sub", username) + .withClaim("iss", host) + .withArrayClaim("roles", new String[] {}) + .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) + .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) + .sign(Algorithm.HMAC256("whatIsIt")); + + when(request.getHeader("Authorization")) + .thenReturn("Bearer " + token); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: " + host, + " algorithm: HS256", + " encoding: plain", + " anonymous: false", + " key: secretFool", + "site:", + " algorithm: RS256", + " encoding: plain", + " anonymous: true", + " default: true"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(request).getHeader("Authorization"); + verify(response).sendError(SC_FORBIDDEN, SynFilter.FORBIDDEN_MSG); + } + + @Test + public void overrideDefaultDeny() throws Exception { + final String servername = "anon-test.com"; + final String host = "http://" + servername; + final String username = "anonymous"; + final List finalRoles = Arrays.asList("islandora", "anonymous", host); + + when(request.getMethod()).thenReturn("GET"); + when(request.getServerName()).thenReturn(servername); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: " + host, + " algorithm: HS256", + " encoding: plain", + " anonymous: true", + " key: secretFool", + "site:", + " algorithm: RS256", + " encoding: plain", + " anonymous: false", + " default: true"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + assertEquals(username, requestCaptor.getValue().getUserPrincipal().getName()); + finalRoles.forEach(role -> assertTrue(requestCaptor.getValue().isUserInRole(role))); + } + + @Test + public void defaultAndSiteAllowed() throws Exception { + final String servername = "anon-test.com"; + final String host = "http://" + servername; + final String username = "anonymous"; + final List finalRoles = Arrays.asList("islandora", "anonymous", host); + + when(request.getMethod()).thenReturn("GET"); + when(request.getServerName()).thenReturn(servername); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " algorithm: RS256", + " encoding: plain", + " anonymous: true", + " default: true"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(chain).doFilter(requestCaptor.capture(), responseCaptor.capture()); + + assertEquals(username, requestCaptor.getValue().getUserPrincipal().getName()); + finalRoles.forEach(role -> assertTrue(requestCaptor.getValue().isUserInRole(role))); + } + + @Test + public void shouldFailSignatureVerification() throws Exception { + final String host = "http://test.com"; + final String token = JWT + .create() + .withClaim("webid", 1) + .withClaim("sub", "normalUser") + .withClaim("iss", host) + .withArrayClaim("roles", new String[] {}) + .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) + .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) + .sign(Algorithm.HMAC256("secret")); + + when(request.getHeader("Authorization")) + .thenReturn("Bearer " + token + "s"); + + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: " + host, + " algorithm: HS256", + " encoding: plain"); + Files.write(Paths.get(this.settings.getAbsolutePath()), testYaml.getBytes()); + + synFilter = createFilter(); + synFilter.doFilter(request, response, chain); + + verify(request).getHeader("Authorization"); + verify(response).sendError(SC_UNAUTHORIZED, SynFilter.UNAUTHORIZED_MSG); + } + + private void createSettings(final File settingsFile) throws Exception { + final String testYaml = String.join("\n", + "---", + "version: 1", + "site:", + " url: http://test.com", + " algorithm: HS256", + " encoding: plain", + " key: secret", + "site:", + " algorithm: HS256", + " encoding: plain", + " default: true", + " key: secret2", + "token:", + " value: 1337"); + Files.write(Paths.get(settingsFile.getAbsolutePath()), testYaml.getBytes()); + } + +} diff --git a/src/test/java/ca/islandora/syn/valves/SynValveTest.java b/src/test/java/ca/islandora/syn/valves/SynValveTest.java deleted file mode 100644 index c94d5f4..0000000 --- a/src/test/java/ca/islandora/syn/valves/SynValveTest.java +++ /dev/null @@ -1,643 +0,0 @@ -package ca.islandora.syn.valves; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.util.Arrays; -import java.util.Date; -import java.util.List; - -import org.apache.catalina.Container; -import org.apache.catalina.Context; -import org.apache.catalina.Host; -import org.apache.catalina.Realm; -import org.apache.catalina.Valve; -import org.apache.catalina.connector.Request; -import org.apache.catalina.connector.Response; -import org.apache.catalina.realm.GenericPrincipal; -import org.apache.tomcat.util.descriptor.web.SecurityConstraint; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import com.auth0.jwt.JWT; -import com.auth0.jwt.algorithms.Algorithm; - -import ca.islandora.syn.valve.SynValve; - -@RunWith(MockitoJUnitRunner.class) -public class SynValveTest { - private SynValve synValve; - private File settings; - - @Mock - private Container container; - - @Mock - private Realm realm; - - @Mock - private Context context; - - @Mock - private Request request; - - @Mock - private Response response; - - @Mock - private Valve nextValve; - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Mock - private Host mockHost; - - private static ZoneOffset offset; - - @Before - public void setUp() throws Exception { - settings = temporaryFolder.newFile(); - createSettings(settings); - - synValve = new SynValve(); - synValve.setPathname(settings.getAbsolutePath()); - synValve.setContainer(container); - synValve.setNext(nextValve); - - when(container.getRealm()).thenReturn(realm); - when(request.getContext()).thenReturn(context); - when(request.getMethod()).thenReturn("POST"); - offset = ZoneId.systemDefault().getRules().getOffset(Instant.now()); - } - - @Test - public void shouldInvokeNextValveWithoutAuth() throws Exception { - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(null); - - synValve.start(); - synValve.invoke(request, response); - - verify(nextValve).invoke(request, response); - } - - @Test - public void shouldPassAuth() throws Exception { - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - final String host = "http://test.com"; - - final String token = "Bearer " + JWT - .create() - .withClaim("uid", 1) - .withClaim("name", "adminuser") - .withClaim("url", host) - .withArrayClaim("roles", new String[] {"role1", "role2", "role3"}) - .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) - .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) - .sign(Algorithm.HMAC256("secret")); - - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn(token); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).getHeader("Authorization"); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(request).setAuthType("SYN"); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("adminuser", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(5, roles.size()); - assertTrue(roles.contains("role1")); - assertTrue(roles.contains("role2")); - assertTrue(roles.contains("role3")); - assertTrue(roles.contains("islandora")); - assertTrue(roles.contains("http://test.com")); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void shouldPassAuthToken() throws Exception { - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - final String token = "Bearer 1337"; - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn(token); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).getHeader("Authorization"); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(request).setAuthType("SYN"); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("islandoraAdmin", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(1, roles.size()); - assertTrue(roles.contains("islandora")); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void shouldFailAuthBecauseOfTokenNotSet() throws Exception { - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - - synValve.start(); - synValve.invoke(request, response); - - verify(request).getHeader("Authorization"); - verify(response).sendError(401, "Token authentication failed."); - } - - @Test - public void shouldFailAuthBecauseOfTokenInvalid1() throws Exception { - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn("garbage"); - - synValve.start(); - synValve.invoke(request, response); - - verify(request).getHeader("Authorization"); - verify(response).sendError(401, "Token authentication failed."); - } - - @Test - public void shouldFailAuthBecauseOfTokenInvalid2() throws Exception { - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn("killer bandit foo"); - - synValve.start(); - synValve.invoke(request, response); - - verify(request).getHeader("Authorization"); - verify(response).sendError(401, "Token authentication failed."); - } - - @Test - public void shouldFailTokenMissingUid() throws Exception { - final String host = "http://test.com"; - final String token = JWT - .create() - .withClaim("name", "adminuser") - .withClaim("url", host) - .withArrayClaim("roles", new String[] {"role1", "role2", "role3"}) - .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) - .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) - .sign(Algorithm.HMAC256("secret")); - - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn("Bearer " + token); - - synValve.start(); - synValve.invoke(request, response); - - verify(request).getHeader("Authorization"); - verify(response).sendError(401, "Token authentication failed."); - } - - @Test - public void shouldPassAuthDefaultSite() throws Exception { - final String host = "http://test2.com"; - final String token = JWT - .create() - .withClaim("uid", 1) - .withClaim("name", "normalUser") - .withClaim("url", host) - .withArrayClaim("roles", new String[] {}) - .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) - .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) - .sign(Algorithm.HMAC256("secret2")); - - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn("Bearer " + token); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).getHeader("Authorization"); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(request).setAuthType("SYN"); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("normalUser", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(2, roles.size()); - assertTrue(roles.contains("islandora")); - assertTrue(roles.contains("http://test2.com")); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void shouldFailAuthBecauseNoSiteMatch() throws Exception { - final String host = "http://test-no-match.com"; - final String token = JWT - .create() - .withClaim("uid", 1) - .withClaim("name", "normalUser") - .withClaim("url", host) - .withArrayClaim("roles", new String[] {}) - .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) - .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) - .sign(Algorithm.HMAC256("secret")); - - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn("Bearer " + token); - - final String testXml = String.join("\n" - , "" - , " " - , "secret" - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - verify(request).getHeader("Authorization"); - verify(response).sendError(401, "Token authentication failed."); - } - - @Test - public void allowAuthWithToken() throws Exception { - final String host = "http://anon-test.com"; - final String token = JWT - .create() - .withClaim("uid", 1) - .withClaim("name", "normalUser") - .withClaim("url", host) - .withArrayClaim("roles", new String[] {}) - .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) - .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) - .sign(Algorithm.HMAC256("secretFool")); - - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn("Bearer " + token); - - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - - final String testXml = String.join("\n" - , "" - , " " - , "secretFool" - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).getHeader("Authorization"); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(request).setAuthType("SYN"); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("normalUser", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(2, roles.size()); - assertTrue(roles.contains("islandora")); - assertTrue(roles.contains(host)); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void allowGetWithoutToken() throws Exception { - final String host = "http://anon-test.com"; - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getMethod()).thenReturn("GET"); - when(mockHost.toString()).thenReturn(host); - when(request.getHost()).thenReturn(mockHost); - - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - - final String testXml = String.join("\n" - , "" - , " " - , "secretFool" - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("anonymous", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(2, roles.size()); - assertTrue(roles.contains("anonymous")); - assertTrue(roles.contains("islandora")); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void allowHeadWithoutToken() throws Exception { - final String host = "http://anon-test.com"; - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getMethod()).thenReturn("HEAD"); - when(mockHost.toString()).thenReturn(host); - when(request.getHost()).thenReturn(mockHost); - - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - - final String testXml = String.join("\n" - , "" - , " " - , "secretFool" - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("anonymous", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(2, roles.size()); - assertTrue(roles.contains("anonymous")); - assertTrue(roles.contains("islandora")); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void disallowGetWithoutToken() throws Exception { - final String host = "http://anon-test.com"; - final String nohost = "http://other-site.com"; - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getMethod()).thenReturn("GET"); - when(mockHost.toString()).thenReturn(host); - when(request.getHost()).thenReturn(mockHost); - - final String testXml = String.join("\n" - , "" - , " " - , "secretFool" - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - verify(request).getHeader("Authorization"); - verify(response).sendError(401, "Token authentication failed."); - } - - @Test - public void overrideDefaultAllow() throws Exception { - final String host = "http://anon-test.com"; - final String token = JWT - .create() - .withClaim("uid", 1) - .withClaim("name", "normalUser") - .withClaim("url", host) - .withArrayClaim("roles", new String[] {}) - .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) - .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) - .sign(Algorithm.HMAC256("secretFool")); - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn("Bearer " + token); - when(request.getMethod()).thenReturn("GET"); - when(mockHost.toString()).thenReturn(host); - when(request.getHost()).thenReturn(mockHost); - - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - - final String testXml = String.join("\n" - , "" - , " " - , "secretFool" - , " " - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("normalUser", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(2, roles.size()); - assertTrue(roles.contains("islandora")); - assertTrue(roles.contains(host)); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void overrideDefaultDeny() throws Exception { - final String host = "http://anon-test.com"; - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getMethod()).thenReturn("GET"); - when(mockHost.toString()).thenReturn(host); - when(request.getHost()).thenReturn(mockHost); - - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - - final String testXml = String.join("\n" - , "" - , " " - , "secretFool" - , " " - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("anonymous", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(2, roles.size()); - assertTrue(roles.contains("anonymous")); - assertTrue(roles.contains("islandora")); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void defaultAndSiteAllowed() throws Exception { - final String host = "http://anon-test.com"; - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getMethod()).thenReturn("GET"); - when(mockHost.toString()).thenReturn(host); - when(request.getHost()).thenReturn(mockHost); - - final ArgumentCaptor argument = ArgumentCaptor.forClass(GenericPrincipal.class); - - final String testXml = String.join("\n" - , "" - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - final InOrder inOrder = inOrder(request, nextValve); - inOrder.verify(request).setUserPrincipal(argument.capture()); - inOrder.verify(nextValve).invoke(request, response); - - assertEquals("anonymous", argument.getValue().getName()); - final List roles = Arrays.asList(argument.getValue().getRoles()); - assertEquals(2, roles.size()); - assertTrue(roles.contains("anonymous")); - assertTrue(roles.contains("islandora")); - assertNull(argument.getValue().getPassword()); - } - - @Test - public void shouldFailSignatureVerification() throws Exception { - final String host = "http://test.com"; - final String token = JWT - .create() - .withClaim("uid", 1) - .withClaim("name", "normalUser") - .withClaim("url", host) - .withArrayClaim("roles", new String[] {}) - .withIssuedAt(Date.from(LocalDateTime.now().toInstant(offset))) - .withExpiresAt(Date.from(LocalDateTime.now().plusHours(2).toInstant(offset))) - .sign(Algorithm.HMAC256("secret")); - - final SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setAuthConstraint(true); - when(realm.findSecurityConstraints(request, request.getContext())) - .thenReturn(new SecurityConstraint[] { securityConstraint }); - when(request.getHeader("Authorization")) - .thenReturn("Bearer " + token + "s"); - - final String testXml = String.join("\n" - , "" - , " " - , "secret" - , " " - , "" - ); - Files.write(Paths.get(this.settings.getAbsolutePath()), testXml.getBytes()); - - synValve.start(); - synValve.invoke(request, response); - - verify(request).getHeader("Authorization"); - verify(response).sendError(401, "Token authentication failed."); - } - - private void createSettings(final File settingsFile) throws Exception { - final String testXml = String.join("\n" - , "" - , " " - , "secret" - , " " - , " " - , "secret2" - , " " - , " " - , "1337" - , " " - , "" - ); - Files.write(Paths.get(settingsFile.getAbsolutePath()), testXml.getBytes()); - } -} diff --git a/src/test/resources/exampleSettings.yaml b/src/test/resources/exampleSettings.yaml new file mode 100644 index 0000000..f014962 --- /dev/null +++ b/src/test/resources/exampleSettings.yaml @@ -0,0 +1,4 @@ +version: 1 +token: + user: bob + value: bobsPassword \ No newline at end of file