diff --git a/src/main/groovy/com/jdbernard/nlsongs/model/ApiKey.groovy b/src/main/groovy/com/jdbernard/nlsongs/model/ApiKey.groovy deleted file mode 100644 index 06d1f02..0000000 --- a/src/main/groovy/com/jdbernard/nlsongs/model/ApiKey.groovy +++ /dev/null @@ -1,7 +0,0 @@ -package com.jdbernard.nlsongs.model - -public class ApiKey implements Serializable { - - String key - String description -} diff --git a/src/main/groovy/com/jdbernard/nlsongs/model/Role.java b/src/main/groovy/com/jdbernard/nlsongs/model/Role.java new file mode 100644 index 0000000..19451a8 --- /dev/null +++ b/src/main/groovy/com/jdbernard/nlsongs/model/Role.java @@ -0,0 +1,3 @@ +package com.jdbernard.nlsongs.model; + +public enum Role { admin, user } diff --git a/src/main/groovy/com/jdbernard/nlsongs/model/Token.groovy b/src/main/groovy/com/jdbernard/nlsongs/model/Token.groovy new file mode 100644 index 0000000..02fb335 --- /dev/null +++ b/src/main/groovy/com/jdbernard/nlsongs/model/Token.groovy @@ -0,0 +1,17 @@ +package com.jdbernard.nlsongs.model + +public class Token implements Serializable { + + String token + User user + Date expires + + @Override + public boolean equals(Object thatObj) { + if (thatObj == null) return false + if (!(thatObj instanceof Token)) return false + + Token that = (Token) thatObj + + return (this.token == that?.token) } +} diff --git a/src/main/groovy/com/jdbernard/nlsongs/model/User.groovy b/src/main/groovy/com/jdbernard/nlsongs/model/User.groovy index 18b17f5..410bd96 100644 --- a/src/main/groovy/com/jdbernard/nlsongs/model/User.groovy +++ b/src/main/groovy/com/jdbernard/nlsongs/model/User.groovy @@ -1,8 +1,17 @@ package com.jdbernard.nlsongs.model +import com.lambdaworks.crypto.SCryptUtil + public class User { int id String username String pwd + Role role + + public void setPwd(String pwd) { + this.pwd = SCryptUtil.scrypt(pwd, 16384, 16, 1) } + + public boolean checkPwd(String givenPwd) { + return SCryptUtil.check(this.pwd, givenPwd) } } diff --git a/src/main/groovy/com/jdbernard/nlsongs/rest/security/NLSongsSecurityContext.groovy b/src/main/groovy/com/jdbernard/nlsongs/rest/security/NLSongsSecurityContext.groovy new file mode 100644 index 0000000..1f5ff9e --- /dev/null +++ b/src/main/groovy/com/jdbernard/nlsongs/rest/security/NLSongsSecurityContext.groovy @@ -0,0 +1,43 @@ +package com.jdbernard.nlsongs.rest.security + +import java.security.Principal +import javax.ws.rs.container.ContainerRequestContext +import javax.ws.rs.core.SecurityContext + +import com.jdbernard.nlsongs.model.Role +import com.jdbernard.nlsongs.model.Token +import com.jdbernard.nlsongs.servlet.NLSongsContext + +public class NLSongsSecurityContext implements SecurityContext { + + public final TokenPrincipal principal + + public NLSongsSecurityContext(ContainerRequestContext ctx) { + + // Extract the authentication token (if present) + String tokenVal = ctx.getHeaderString("Authorization-Token") + + // Look up the token in the database. + Token token = NLSongsContext.songsDB.findToken(tokenVal) + + // Create our principal based on this token. + this.principal = new TokenPrincipal(token) + } + + @Override + public String getAuthenticationScheme() { return "Authentication-Token" } + + @Override + public Principal getUserPrincipal() { return principal } + + @Override + public boolean isSecure() { /* TODO */ return false } + + @Override + public boolean isUserInRole(String role) { + println "Required Role: $role" + println "User's Role: ${principal?.token?.user?.role}" + println "Required Role == User's Role? ${principal?.token?.user?.role == ((Role)role)} " + return principal?.token?.user?.role == ((Role) role) } +} + diff --git a/src/main/groovy/com/jdbernard/nlsongs/rest/security/SecurityRequestFilter.java b/src/main/groovy/com/jdbernard/nlsongs/rest/security/SecurityRequestFilter.java new file mode 100644 index 0000000..eef1b9a --- /dev/null +++ b/src/main/groovy/com/jdbernard/nlsongs/rest/security/SecurityRequestFilter.java @@ -0,0 +1,17 @@ +package com.jdbernard.nlsongs.rest.security; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.PreMatching; +import javax.ws.rs.ext.Provider; + +import com.jdbernard.nlsongs.servlet.NLSongsContext; + +@Provider +@PreMatching +public class SecurityRequestFilter implements ContainerRequestFilter { + + @Override + public void filter(ContainerRequestContext reqCtx) { + reqCtx.setSecurityContext(new NLSongsSecurityContext(reqCtx)); } +} diff --git a/src/main/groovy/com/jdbernard/nlsongs/rest/security/TokenPrincipal.java b/src/main/groovy/com/jdbernard/nlsongs/rest/security/TokenPrincipal.java new file mode 100644 index 0000000..6809f10 --- /dev/null +++ b/src/main/groovy/com/jdbernard/nlsongs/rest/security/TokenPrincipal.java @@ -0,0 +1,35 @@ +package com.jdbernard.nlsongs.rest.security; + +import java.security.Principal; + +import com.jdbernard.nlsongs.model.Token; + +public class TokenPrincipal implements Principal { + public final Token token; + + public TokenPrincipal(Token token) { this.token = token; } + + @Override + public boolean equals(Object thatObj) { + if (thatObj == null) return false; + if (!(thatObj instanceof TokenPrincipal)) return false; + + TokenPrincipal that = (TokenPrincipal) thatObj; + + if (this.token == null) { return that.token == null; } + else { return this.token.equals(that.token); } } + + @Override + public String getName() { + if (this.token == null) return null; + else return this.token.getUser().getUsername(); } + + @Override + public int hashCode() { + if (this.token == null) return 0; + return this.token.getUser().getUsername().hashCode(); } + + @Override + public String toString() { return getName(); } +} + diff --git a/src/main/sql/create-db.sql b/src/main/sql/create-db.sql index c892650..3455fa2 100644 --- a/src/main/sql/create-db.sql +++ b/src/main/sql/create-db.sql @@ -46,15 +46,18 @@ CREATE TABLE IF NOT EXISTS performances ( FOREIGN KEY (song_id) REFERENCES songs (id)); -DROP TABLE IF EXISTS api_keys; -CREATE TABLE IF NOT EXISTS api_keys ( - key VARCHAR(32) NOT NULL, - description VARCHAR(256) NOT NULL, - PRIMARY KEY (key)); - - DROP TABLE IF EXISTS users; CREATE TABLE IF NOT EXISTS users ( id SERIAL, - username VARCHAR(64), - pwd VARCHAR(80)); + username VARCHAR(64) UNIQUE NOT NULL, + pwd VARCHAR(80), + role VARCHAR(16) NOT NULL, + PRIMARY KEY (id)); + +DROP TABLE IF EXISTS tokens; +CREATE TABLE IF NOT EXISTS tokens ( + token VARCHAR(64), + user_id INTEGER NOT NULL, + expires TIMESTAMP NOT NULL, + PRIMARY KEY (token), + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE); diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index b5081e7..8a74708 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -21,6 +21,11 @@ com.jdbernard.nlsongs.rest + + jersey.config.server.provider.classnames + org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature + + 1