diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..b0c23e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +**/.classpath +.DS_Store +**/nb-configuration.xml +**/.springBeans +**/testing.properties +**/metalnx.testing.properties +**/*.eml +**/Scripts +.idea +*.iml +.project +.idea +.metadata +.DS_Store +Servers +.settings +.classpath +target +mvn-repo +bin +*.war +*.log +*/nbactions.xml +*.eml +nbactions.xml +testing.properties +*/test-output +JargonVersion.java +**/${test.option.mount.basedir} +**/*.*~ +*.*~ +.dbeaver* +test.metalnx.properties +**/.vscode/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..0772a1d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +### Added + +### Changed + +### Removed + diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..ce733ff --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, DataNet Federation Consortium +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..8645077 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ + +Jargon Extensions API for irods-ext + + +# Project: Jargon Extensions JWT +### Date: +### Release Version: 4.3.1.0-SNAPSHOT +### git tag: + +## Description + +Supports JWT creation and decoding for iRODS related microservices t + +## Requirements + +* Jargon depends on Java 1.8+ +* Jargon is built using Apache Maven2, see POM for dependencies + +## Libraries + +Jargon-core uses Maven for dependency management. See the pom.xml file for references to various dependencies. + +Note that the following bug and feature requests are logged in GitHub with related commit information [[https://github.com/DICE-UNC/jargon-extensions-jwt/issues]] + diff --git a/pom.xml b/pom.xml new file mode 100755 index 0000000..3e0ea0a --- /dev/null +++ b/pom.xml @@ -0,0 +1,151 @@ + + + 4.0.0 + + org.irods + jargon-pom + 4.3.1.0-SNAPSHOT + + irodsext-jwt-service + irodsext-jwt-service + + + junit + junit + compile + + + io.jsonwebtoken + jjwt-api + + + io.jsonwebtoken + jjwt-impl + runtime + + + io.jsonwebtoken + jjwt-jackson + runtime + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + org.mockito + mockito-all + test + + + + + dice.repository snaps + dice.repository.snapshots + https://raw.github.com/DICE-UNC/DICE-Maven/master/snapshots + + true + + + true + always + warn + + + + dice.repository + dice.repository + https://raw.github.com/DICE-UNC/DICE-Maven/master/releases + + true + + + false + always + warn + + + + netbeans.repository + netbeans.repository + http://bits.netbeans.org/maven2 + + true + + + true + never + fail + + + + Tools for managing jwts used by associated microservices + + + + maven-antrun-plugin + + + 0 + validate + + + + + + test.confirm=${jargon.test.confirm} + test.data.directory=${jargon.test.data.directory} + test.irods.admin=${jargon.test.irods.admin} + test.irods.admin.password=${jargon.test.irods.admin.password} + test.irods.user=${jargon.test.irods.user} + test.irods.password=${jargon.test.irods.password} + test.irods.resource=${jargon.test.irods.resource} + test2.irods.user=${jargon.test.irods.user2} + test2.irods.password=${jargon.test.irods.password2} + test2.irods.resource=${jargon.test.irods.resource2} + test3.irods.user=${jargon.test.irods.user3} + test3.irods.password=${jargon.test.irods.password3} + test3.irods.resource=${jargon.test.irods.resource3} + test.irods.host=${jargon.test.irods.host} + test.irods.port=${jargon.test.irods.port} + test.irods.zone=${jargon.test.irods.zone} + test.resource.group=${jargon.test.resource.group} + test.irods.userDN=${jargon.test.irods.userDN} + test.irods.scratch.subdir=${jargon.test.irods.scratch.subdir} + + + + + run + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + true + + + + + + diff --git a/src/main/java/.gitinclude b/src/main/java/.gitinclude new file mode 100755 index 0000000..e69de29 diff --git a/src/main/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImpl.java b/src/main/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImpl.java new file mode 100644 index 0000000..36b0c05 --- /dev/null +++ b/src/main/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImpl.java @@ -0,0 +1,67 @@ +/** + * + */ +package org.irods.jargon.irodsext.jwt; + +import java.security.Key; +import java.util.Date; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; + +/** + * @author Mike Conway - NIEHS + * + */ +public class JwtIssueServiceImpl { + + public static final Logger log = LoggerFactory.getLogger(JwtIssueServiceImpl.class); + + private final JwtServiceConfig jwtServiceConfig; + private final Key myKey; + + /** + * Constructor with configs + * + * @param jwtServiceConfig {@link JwtServiceConfig} + */ + public JwtIssueServiceImpl(final JwtServiceConfig jwtServiceConfig) { + if (jwtServiceConfig == null) { + throw new IllegalArgumentException("null jwtServiceConfig"); + } + + this.jwtServiceConfig = jwtServiceConfig; + myKey = Keys.hmacShaKeyFor(jwtServiceConfig.getSecret().getBytes()); + } + + public String issueJwtToken(final String subject) { + log.info("issueJwtToken()"); + + if (subject == null || subject.isEmpty()) { + throw new IllegalArgumentException("null or empty subject"); + } + + String signedJwt = Jwts.builder().setSubject(subject).setIssuer(jwtServiceConfig.getIssuer()) + .setIssuedAt(new Date()).signWith(myKey).compact(); + return signedJwt; + + } + + public Jws decodeJwtToken(final String token) { + log.info("decodeJwtToken()"); + + if (token == null || token.isEmpty()) { + throw new IllegalArgumentException("null or empty token"); + } + + Jws claims = Jwts.parser().setSigningKey(myKey).parseClaimsJws(token); + return claims; + + } + +} diff --git a/src/main/java/org/irods/jargon/irodsext/jwt/JwtServiceConfig.java b/src/main/java/org/irods/jargon/irodsext/jwt/JwtServiceConfig.java new file mode 100644 index 0000000..89f3b8b --- /dev/null +++ b/src/main/java/org/irods/jargon/irodsext/jwt/JwtServiceConfig.java @@ -0,0 +1,59 @@ +/** + * + */ +package org.irods.jargon.irodsext.jwt; + +/** + * Basic configs for a service to issue and decode jwts used in iRODS + * microservices + * + * @author Mike Conway - NIEHS + * + */ +public class JwtServiceConfig { + + /** + * Issuer typically in reverse dns name format, used as "iss" in the JWT + */ + private String issuer = ""; + /** + * Secret used to sign tokens given the provided algo + */ + private String secret = ""; + /** + * Signing algo used in JWT + */ + private String algo = ""; + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + public String getSecret() { + return secret; + } + + public void setSecret(String secret) { + this.secret = secret; + } + + public String getAlgo() { + return algo; + } + + public void setAlgo(String algo) { + this.algo = algo; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("JwtServiceConfig [issuer=").append(issuer).append(", algo=").append(algo).append("]"); + return builder.toString(); + } + +} diff --git a/src/main/java/org/irods/jargon/irodsext/jwt/package-info.java b/src/main/java/org/irods/jargon/irodsext/jwt/package-info.java new file mode 100644 index 0000000..211f488 --- /dev/null +++ b/src/main/java/org/irods/jargon/irodsext/jwt/package-info.java @@ -0,0 +1,9 @@ + +/** + * Utils and services to support use of JWT tokens in Jargon extensions and + * microservices + * + * @author Mike Conway - NIEHS + * + */ +package org.irods.jargon.irodsext.jwt; \ No newline at end of file diff --git a/src/main/resources/.gitinclude b/src/main/resources/.gitinclude new file mode 100755 index 0000000..e69de29 diff --git a/src/test/java/.gitinclude b/src/test/java/.gitinclude new file mode 100755 index 0000000..e69de29 diff --git a/src/test/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImplTest.java b/src/test/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImplTest.java new file mode 100644 index 0000000..7b4d1b1 --- /dev/null +++ b/src/test/java/org/irods/jargon/irodsext/jwt/JwtIssueServiceImplTest.java @@ -0,0 +1,47 @@ +package org.irods.jargon.irodsext.jwt; + +import org.junit.Assert; +import org.junit.Test; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.WeakKeyException; + +public class JwtIssueServiceImplTest { + + @Test(expected = WeakKeyException.class) + public void testIssueJwtWeakKey() { + JwtServiceConfig config = new JwtServiceConfig(); + config.setAlgo(SignatureAlgorithm.HS256.getValue()); + config.setIssuer("test"); + config.setSecret("thisisasecret"); + JwtIssueServiceImpl jwtIssueServiceImpl = new JwtIssueServiceImpl(config); + jwtIssueServiceImpl.issueJwtToken("subject"); + } + + @Test + public void testIssueJwt() { + JwtServiceConfig config = new JwtServiceConfig(); + config.setAlgo(SignatureAlgorithm.HS256.getValue()); + config.setIssuer("test"); + config.setSecret("thisisasecretthatisverysecretyouwillneverguessthiskey"); + JwtIssueServiceImpl jwtIssueServiceImpl = new JwtIssueServiceImpl(config); + String jwt = jwtIssueServiceImpl.issueJwtToken("subject"); + Assert.assertNotNull("no jwt issued", jwt); + } + + @Test + public void testIssueAndDecodeJwt() { + JwtServiceConfig config = new JwtServiceConfig(); + config.setAlgo(SignatureAlgorithm.HS256.getValue()); + config.setIssuer("test"); + config.setSecret("thisisasecretthatisverysecretyouwillneverguessthiskeyhurray"); + JwtIssueServiceImpl jwtIssueServiceImpl = new JwtIssueServiceImpl(config); + String jwt = jwtIssueServiceImpl.issueJwtToken("subject"); + Jws actual = jwtIssueServiceImpl.decodeJwtToken(jwt); + Assert.assertNotNull("claims not returned", actual); + + } + +} diff --git a/src/test/java/org/irods/jargon/irodsext/jwt/unittest/AllTests.java b/src/test/java/org/irods/jargon/irodsext/jwt/unittest/AllTests.java new file mode 100644 index 0000000..a1770f5 --- /dev/null +++ b/src/test/java/org/irods/jargon/irodsext/jwt/unittest/AllTests.java @@ -0,0 +1,12 @@ +package org.irods.jargon.irodsext.jwt.unittest; + +import org.irods.jargon.irodsext.jwt.JwtIssueServiceImplTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ JwtIssueServiceImplTest.class }) +public class AllTests { + +} diff --git a/src/test/java/org/irods/jargon/irodsext/jwt/unittest/package-info.java b/src/test/java/org/irods/jargon/irodsext/jwt/unittest/package-info.java new file mode 100644 index 0000000..ad82a96 --- /dev/null +++ b/src/test/java/org/irods/jargon/irodsext/jwt/unittest/package-info.java @@ -0,0 +1,10 @@ +/** + * + */ +/** + * Suites and test utils + * + * @author Mike Conway - NIEHS + * + */ +package org.irods.jargon.irodsext.jwt.unittest; \ No newline at end of file diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100755 index 0000000..eed7b65 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,11 @@ +# Set root logger level to DEBUG and its only appender to A1. +#log4j.rootLogger=ERROR, A1 +log4j.category.org.irods.jargon.core=INFO, A1 +log4j.category.org.irods.jargon.datautils=INFO, A1 + +# A1 is set to be a ConsoleAppender. +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n