diff --git a/conf/syn-settings.example.xml b/conf/syn-settings.example.xml index e0613f5..da1aa96 100644 --- a/conf/syn-settings.example.xml +++ b/conf/syn-settings.example.xml @@ -22,4 +22,12 @@ my secret key --> + + + my super secret token + + diff --git a/src/main/java/ca/islandora/syn/settings/SettingsParser.java b/src/main/java/ca/islandora/syn/settings/SettingsParser.java index 8221917..f7755c4 100644 --- a/src/main/java/ca/islandora/syn/settings/SettingsParser.java +++ b/src/main/java/ca/islandora/syn/settings/SettingsParser.java @@ -40,6 +40,10 @@ private static Digester getDigester() { digester.addSetProperties("sites/site"); digester.addCallMethod("sites/site", "setKey", 0); digester.addSetNext("sites/site", "addSite", "ca.islandora.syn.settings.Site"); + digester.addObjectCreate("sites/token", "ca.islandora.syn.settings.Token"); + digester.addSetProperties("sites/token"); + digester.addCallMethod("sites/token", "setToken", 0); + digester.addSetNext("sites/token", "addToken", "ca.islandora.syn.settings.Token"); } return digester; } @@ -168,19 +172,28 @@ private static Algorithm getHmacAlgorithm(final Site site) { } } - public static Map getSiteAlgorithms(final InputStream settings) { - final Map algorithms = new HashMap<>(); + private static Sites getSites(final InputStream settings) { Sites sites; try { sites = getSitesObject(settings); } catch (Exception e) { log.error("Error loading settings file.", e); - return algorithms; + return null; } if (sites.getVersion() != 1) { log.error("Incorrect XML version. Aborting."); + return null; + } + + return sites; + } + + public static Map getSiteAlgorithms(final InputStream settings) { + final Map algorithms = new HashMap<>(); + final Sites sites = getSites(settings); + if (sites == null) { return algorithms; } @@ -236,6 +249,24 @@ public static Map getSiteAlgorithms(final InputStream setting return algorithms; } + public static Map getSiteStaticTokens(final InputStream settings) { + final Map tokens = new HashMap<>(); + final Sites sites = getSites(settings); + if (sites == null) { + return tokens; + } + + for (Token token : sites.getTokens()) { + if (token.getToken().isEmpty()) { + log.error("Static token is empty ignoring."); + } else { + tokens.put(token.getToken(), token); + } + } + + return tokens; + } + static Sites getSitesObject(final InputStream settings) throws IOException, SAXException { return (Sites) getDigester().parse(settings); diff --git a/src/main/java/ca/islandora/syn/settings/Sites.java b/src/main/java/ca/islandora/syn/settings/Sites.java index 3f7d38c..9b52244 100644 --- a/src/main/java/ca/islandora/syn/settings/Sites.java +++ b/src/main/java/ca/islandora/syn/settings/Sites.java @@ -6,6 +6,7 @@ public class Sites { private int version = -1; private List sites = new ArrayList<>(); + private List tokens = new ArrayList<>(); public void addSite(final Site site) { sites.add(site); @@ -20,4 +21,11 @@ public int getVersion() { public void setVersion(final int version) { this.version = version; } + + public void addToken(final Token token) { + tokens.add(token); + } + public List getTokens() { + return tokens; + } } diff --git a/src/main/java/ca/islandora/syn/settings/Token.java b/src/main/java/ca/islandora/syn/settings/Token.java new file mode 100644 index 0000000..3548d72 --- /dev/null +++ b/src/main/java/ca/islandora/syn/settings/Token.java @@ -0,0 +1,39 @@ +package ca.islandora.syn.settings; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Token { + private String user = "islandoraAdmin"; + private List roles = new ArrayList<>(); + private String token = ""; + + public String getUser() { + return user; + } + + public void setUser(final String user) { + this.user = user; + } + + public List getRoles() { + return roles; + } + + public void setRoles(final String roles) { + this.roles.clear(); + if (!roles.isEmpty()) { + final String[] parts = roles.split(","); + Collections.addAll(this.roles,parts); + } + } + + public String getToken() { + return token; + } + + public void setToken(final String token) { + this.token = token.trim(); + } +} \ No newline at end of file diff --git a/src/main/java/ca/islandora/syn/valve/SynValve.java b/src/main/java/ca/islandora/syn/valve/SynValve.java index fe3cd59..e198fd6 100644 --- a/src/main/java/ca/islandora/syn/valve/SynValve.java +++ b/src/main/java/ca/islandora/syn/valve/SynValve.java @@ -1,6 +1,7 @@ package ca.islandora.syn.valve; import ca.islandora.syn.settings.SettingsParser; +import ca.islandora.syn.settings.Token; import ca.islandora.syn.token.Verifier; import java.io.File; import java.io.FileInputStream; @@ -24,6 +25,7 @@ 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; @Override public void invoke(final Request request, final Response response) @@ -64,6 +66,15 @@ private boolean doAuthentication(final Request request) { // 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."); @@ -94,6 +105,14 @@ private boolean doAuthentication(final Request request) { } } + 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"); @@ -135,6 +154,7 @@ public synchronized void startInternal() throws LifecycleException { // Load the contents of the database file try { this.algorithmMap = SettingsParser.getSiteAlgorithms(new FileInputStream(file)); + this.staticTokenMap = SettingsParser.getSiteStaticTokens(new FileInputStream(file)); } catch (Exception e) { throw new LifecycleException("Error parsing XML Configuration", e); } diff --git a/src/test/java/ca/islandora/syn/settings/SettingsParserTokenTest.java b/src/test/java/ca/islandora/syn/settings/SettingsParserTokenTest.java new file mode 100644 index 0000000..2cf50a1 --- /dev/null +++ b/src/test/java/ca/islandora/syn/settings/SettingsParserTokenTest.java @@ -0,0 +1,82 @@ +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; + +public class SettingsParserTokenTest { + @Test + public void testInvalidVersion() throws Exception { + final String testXml = String.join("\n" + , "" + , " " + , " c00lpazzward" + , " " + , "" + ); + + final InputStream stream = new ByteArrayInputStream(testXml.getBytes()); + final Map tokens = SettingsParser.getSiteStaticTokens(stream); + assertEquals(0, tokens.size()); + } + + @Test + public void testTokenNoParams() throws Exception { + final String testXml = String.join("\n" + , "" + , " " + , " 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()); + } + + @Test + public void testTokenUser() throws Exception { + final String testXml = String.join("\n" + , "" + , " " + , " 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()); + } + + @Test + public void testTokenRole() throws Exception { + final String testXml = String.join("\n" + , "" + , " " + , " 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")); + } +} diff --git a/src/test/java/ca/islandora/syn/settings/SitesTest.java b/src/test/java/ca/islandora/syn/settings/SitesTest.java index f8b56cb..e63beda 100644 --- a/src/test/java/ca/islandora/syn/settings/SitesTest.java +++ b/src/test/java/ca/islandora/syn/settings/SitesTest.java @@ -18,4 +18,14 @@ public void TestJwtSites() { assertEquals(1, sites.getSites().size()); assertEquals(site, sites.getSites().get(0)); } + + @Test + public void TestToken() { + final Sites sites = new Sites(); + final Token token = new Token(); + assertEquals(0, sites.getTokens().size()); + sites.addToken(token); + assertEquals(1, sites.getTokens().size()); + assertEquals(token, sites.getTokens().get(0)); + } } diff --git a/src/test/java/ca/islandora/syn/settings/TokenTest.java b/src/test/java/ca/islandora/syn/settings/TokenTest.java new file mode 100644 index 0000000..5f6647a --- /dev/null +++ b/src/test/java/ca/islandora/syn/settings/TokenTest.java @@ -0,0 +1,50 @@ +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; + +public class TokenTest { + Token token; + + @Before + public void initializeSite() { + this.token = new Token(); + } + + @Test + public void testTokenUser() { + assertEquals("islandoraAdmin", this.token.getUser()); + final String testVal = "test"; + this.token.setUser(testVal); + assertEquals(testVal, this.token.getUser()); + } + + @Test + public void testTokenRoles() { + assertEquals(0, this.token.getRoles().size()); + + this.token.setRoles("test"); + assertEquals(1, this.token.getRoles().size()); + assertEquals("test", this.token.getRoles().get(0)); + + this.token.setRoles("this,is,a,test"); + assertEquals(4, this.token.getRoles().size()); + assertEquals("this", this.token.getRoles().get(0)); + assertEquals("is", this.token.getRoles().get(1)); + assertEquals("a", this.token.getRoles().get(2)); + assertEquals("test", this.token.getRoles().get(3)); + } + + @Test + public void testTokenToken() { + assertTrue(this.token.getToken().isEmpty()); + final String testVal = "test"; + this.token.setToken(testVal); + assertEquals(testVal, this.token.getToken()); + this.token.setToken(" " + testVal); + assertEquals(testVal, this.token.getToken()); + } +} diff --git a/src/test/java/ca/islandora/syn/valves/SynValveTest.java b/src/test/java/ca/islandora/syn/valves/SynValveTest.java index 62f85c9..7aa396c 100644 --- a/src/test/java/ca/islandora/syn/valves/SynValveTest.java +++ b/src/test/java/ca/islandora/syn/valves/SynValveTest.java @@ -128,6 +128,33 @@ public void shouldPassAuth() throws Exception { 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(); @@ -281,6 +308,9 @@ private void createSettings(final File settingsFile) throws Exception { , " " , "secret2" , " " + , " " + , "1337" + , " " , "" ); Files.write(Paths.get(settingsFile.getAbsolutePath()), testXml.getBytes());