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