diff --git a/pom.xml b/pom.xml index f353996..0e89f4b 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 kangaroo-and-rabbit archidata - 0.2.6 + 0.3.1 2.1 2.32 diff --git a/src/org/kar/archidata/MigrationAction.java b/src/org/kar/archidata/MigrationAction.java new file mode 100644 index 0000000..61d8495 --- /dev/null +++ b/src/org/kar/archidata/MigrationAction.java @@ -0,0 +1,5 @@ +package org.kar.archidata; + +public class MigrationAction { + +} diff --git a/src/org/kar/archidata/SqlWrapper.java b/src/org/kar/archidata/SqlWrapper.java index dae1b4c..eec4080 100644 --- a/src/org/kar/archidata/SqlWrapper.java +++ b/src/org/kar/archidata/SqlWrapper.java @@ -616,6 +616,45 @@ public class SqlWrapper { public static List getsWhere(Class clazz, List condition, boolean full, Integer linit) throws Exception { return getsWhere(clazz, condition, null, full, linit); } + public static void whereAppendQuery(StringBuilder query, String tableName, List condition, boolean exclude_deleted) throws ExceptionDBInterface { + // Check if we have a condition to generate + if (condition == null || condition.size() == 0) { + return; + } + query.append(" WHERE "); + boolean first = true; + for (WhereCondition elem : condition) { + if (first) { + first = false; + } else { + query.append(" AND "); + } + query.append(tableName); + query.append("."); + query.append(elem.key()); + query.append(" "); + query.append(elem.comparator()); + query.append(" ?"); + } + if (exclude_deleted) { + if (!first) { + query.append(" AND "); + } + query.append(tableName); + query.append(".deleted = false "); + } + } + public static void whereInjectValue(PreparedStatement ps, List condition) throws Exception { + // Check if we have a condition to generate + if (condition == null || condition.size() == 0) { + return; + } + int iii = 1; + for (WhereCondition elem : condition) { + addElement(ps, elem.Value(), iii++); + } + } + public static List getsWhere(Class clazz, List condition, String orderBy, boolean full, Integer linit) throws Exception { DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); List outs = new ArrayList<>(); @@ -659,24 +698,7 @@ public class SqlWrapper { query.append(" FROM `"); query.append(tableName); query.append("` "); - query.append(" WHERE "); - if (condition.size() == 0) { - throw new ExceptionDBInterface(4575643, tableName + " ==> request where without parameters"); - } - boolean first = true; - for (WhereCondition elem : condition) { - if (first) { - first = false; - } else { - query.append(" AND "); - } - query.append(tableName); - query.append("."); - query.append(elem.key()); - query.append(" "); - query.append(elem.comparator()); - query.append(" ?"); - } + whereAppendQuery(query, tableName, condition, true); if (orderBy != null && orderBy.length() >= 1) { query.append(" ORDER BY "); //query.append(tableName); @@ -695,10 +717,7 @@ public class SqlWrapper { //System.out.println("generate the query: '" + query.toString() + "'"); // prepare the request: PreparedStatement ps = entry.connection.prepareStatement(query.toString(), Statement.RETURN_GENERATED_KEYS); - int iii = 1; - for (WhereCondition elem : condition) { - addElement(ps, elem.Value(), iii++); - } + whereInjectValue(ps, condition); // execute the request ResultSet rs = ps.executeQuery(); while (rs.next()) { @@ -1000,35 +1019,51 @@ public class SqlWrapper { public static void delete(Class clazz, long id) throws Exception { // TODO: I am not sure this is a real good idea. } - public static void setDelete(Class clazz, long id) throws Exception { + public static int setDelete(Class clazz, long id) throws Exception { + return setDeleteWhere(clazz, List.of( + new WhereCondition("id", "=", id) + )); + } + + public static int setDeleteWhere(Class clazz, List condition) throws Exception { String tableName = getTableName(clazz); DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); - String query = "UPDATE `" + tableName + "` SET `modify_date`=now(3), `deleted`=true WHERE `id` = ?"; + StringBuilder query = new StringBuilder(); + query.append("UPDATE `"); + query.append(tableName); + query.append("` SET `modify_date`=now(3), `deleted`=true "); + whereAppendQuery(query, tableName, condition, false); try { - PreparedStatement ps = entry.connection.prepareStatement(query); - int iii = 1; - ps.setLong(iii++, id); - ps.executeUpdate(); - } catch (SQLException ex) { - ex.printStackTrace(); - throw new ExceptionDBInterface(500, "SQL error: " + ex.getMessage()); + PreparedStatement ps = entry.connection.prepareStatement(query.toString()); + whereInjectValue(ps, condition); + int affectedRows = ps.executeUpdate(); + return affectedRows; } finally { entry.close(); entry = null; } } - public static void unsetDelete(Class clazz, long id) throws Exception { + + + public static int unsetDelete(Class clazz, long id) throws Exception { + return unsetDeleteWhere(clazz, List.of( + new WhereCondition("id", "=", id) + )); + } + + public static int unsetDeleteWhere(Class clazz, List condition) throws Exception { String tableName = getTableName(clazz); DBEntry entry = new DBEntry(GlobalConfiguration.dbConfig); - String query = "UPDATE `" + tableName + "` SET `modify_date`=now(3), `deleted`=false WHERE `id` = ?"; + StringBuilder query = new StringBuilder(); + query.append("UPDATE `"); + query.append(tableName); + query.append("` SET `modify_date`=now(3), `deleted`=false "); + whereAppendQuery(query, tableName, condition, false); try { - PreparedStatement ps = entry.connection.prepareStatement(query); - int iii = 1; - ps.setLong(iii++, id); - ps.executeUpdate(); - } catch (SQLException ex) { - ex.printStackTrace(); - throw new ExceptionDBInterface(500, "SQL error: " + ex.getMessage()); + PreparedStatement ps = entry.connection.prepareStatement(query.toString()); + whereInjectValue(ps, condition); + int affectedRows = ps.executeUpdate(); + return affectedRows; } finally { entry.close(); entry = null; @@ -1228,4 +1263,5 @@ public class SqlWrapper { return ((SQLLimitSize) annotation[0]).value(); } + } \ No newline at end of file diff --git a/src/org/kar/archidata/filter/AuthenticationFilter.java b/src/org/kar/archidata/filter/AuthenticationFilter.java index 7ac1e2b..c8af819 100644 --- a/src/org/kar/archidata/filter/AuthenticationFilter.java +++ b/src/org/kar/archidata/filter/AuthenticationFilter.java @@ -20,6 +20,7 @@ import javax.ws.rs.ext.Provider; import org.kar.archidata.UserDB; import org.kar.archidata.annotation.security.PermitTokenInURI; import org.kar.archidata.model.User; +import org.kar.archidata.model.UserByToken; import org.kar.archidata.util.JWTWrapper; import com.nimbusds.jwt.JWTClaimsSet; @@ -40,6 +41,7 @@ public class AuthenticationFilter implements ContainerRequestFilter { private ResourceInfo resourceInfo; private static final String AUTHENTICATION_SCHEME = "Yota"; + private static final String AUTHENTICATION_TOKEN_SCHEME = "Zota"; @Override public void filter(ContainerRequestContext requestContext) throws IOException { @@ -84,63 +86,68 @@ public class AuthenticationFilter implements ContainerRequestFilter { } } } - //System.out.println("authorizationHeader: " + authorizationHeader); - - - /* - System.out.println(" -------------------------------"); - // this get the parameters inside the pre-parsed element in the request ex: @Path("thumbnail/{id}") generate a map with "id" - MultivaluedMap pathparam = requestContext.getUriInfo().getPathParameters(); - for (Entry> item: pathparam.entrySet()) { - System.out.println(" param: " + item.getKey() + " ==>" + item.getValue()); - } - System.out.println(" -------------------------------"); - // need to add "@QueryParam("p") String token, " in the model - //MultivaluedMap quaryparam = requestContext.getUriInfo().getQueryParameters(); - for (Entry> item: quaryparam.entrySet()) { - System.out.println(" query: " + item.getKey() + " ==>" + item.getValue()); - } - System.out.println(" -------------------------------"); - List segments = requestContext.getUriInfo().getPathSegments(); - for (final PathSegment item: segments) { - System.out.println(" query: " + item.getPath() + " ==>" + item.getMatrixParameters()); - } - System.out.println(" -------------------------------"); - MultivaluedMap headers = requestContext.getHeaders(); - for (Entry> item: headers.entrySet()) { - System.out.println(" headers: " + item.getKey() + " ==>" + item.getValue()); - } - System.out.println(" -------------------------------"); - */ - // Validate the Authorization header data Model "Yota userId:token" - if (!isTokenBasedAuthentication(authorizationHeader)) { + // System.out.println("authorizationHeader: " + authorizationHeader); + boolean isApplicationToken = isApplicationTokenBasedAuthentication(authorizationHeader); + boolean isJwtToken = isTokenBasedAuthentication(authorizationHeader); + // Validate the Authorization header data Model "Yota jwt.to.ken" "Zota tokenId:hash(token)" + if (!isApplicationToken && !isJwtToken) { System.out.println("REJECTED unauthorized: " + requestContext.getUriInfo().getPath()); abortWithUnauthorized(requestContext); return; } - // check JWT token (basic:) - - // Extract the token from the Authorization header (Remove "Yota ") - String token = authorizationHeader.substring(AUTHENTICATION_SCHEME.length()).trim(); - System.out.println("token: " + token); - - User user = null; - try { - user = validateToken(token); - } catch (Exception e) { - System.out.println("Fail to validate token: " + e.getMessage()); - abortWithUnauthorized(requestContext); - return; - } - if (user == null) { - System.out.println("get a NULL user ..."); - abortWithUnauthorized(requestContext); - return; + UserByToken userByToken = null; + if (isJwtToken) { + // Extract the token from the Authorization header (Remove "Yota ") + String token = authorizationHeader.substring(AUTHENTICATION_SCHEME.length()).trim(); + //System.out.println("token: " + token); + try { + user = validateJwtToken(token); + } catch (Exception e) { + System.out.println("Fail to validate token: " + e.getMessage()); + abortWithUnauthorized(requestContext); + return; + } + if (user == null) { + System.out.println("get a NULL user ..."); + abortWithUnauthorized(requestContext); + return; + } + // We are in transition phase the user element will be removed + userByToken = new UserByToken(); + userByToken.id = user.id; + userByToken.name = user.login; + userByToken.parentId = null; + userByToken.type = UserByToken.TYPE_USER; + if (user.removed || user.blocked) { + userByToken.type = null; + } else if (user.admin) { + userByToken.right.put("ADMIN", true); + userByToken.right.put("USER", true); + } else { + userByToken.right.put("USER", true); + } + } else { + // Extract the token from the Authorization header (Remove "Zota ") + String token = authorizationHeader.substring(AUTHENTICATION_TOKEN_SCHEME.length()).trim(); + //System.out.println("token: " + token); + try { + userByToken = validateToken(token); + } catch (Exception e) { + System.out.println("Fail to validate token: " + e.getMessage()); + abortWithUnauthorized(requestContext); + return; + } + if (userByToken == null) { + System.out.println("get a NULL application ..."); + abortWithUnauthorized(requestContext); + return; + } + } // create the security context model: String scheme = requestContext.getUriInfo().getRequestUri().getScheme(); - MySecurityContext userContext = new MySecurityContext(user, scheme); + MySecurityContext userContext = new MySecurityContext(user, userByToken, scheme); // retrieve the allowed right: RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class); List roles = Arrays.asList(rolesAnnotation.value()); @@ -152,7 +159,6 @@ public class AuthenticationFilter implements ContainerRequestFilter { break; } } - //Is user valid? if( ! haveRight) { System.out.println("REJECTED not enought right : " + requestContext.getUriInfo().getPath() + " require: " + roles); @@ -160,7 +166,7 @@ public class AuthenticationFilter implements ContainerRequestFilter { return; } requestContext.setSecurityContext(userContext); - System.out.println("Get local user : " + user); + // System.out.println("Get local user : " + user + " / " + userByToken); } private boolean isTokenBasedAuthentication(String authorizationHeader) { @@ -169,6 +175,14 @@ public class AuthenticationFilter implements ContainerRequestFilter { // The authentication scheme comparison must be case-insensitive return authorizationHeader != null && authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " "); } + + private boolean isApplicationTokenBasedAuthentication(String authorizationHeader) { + // Check if the Authorization header is valid + // It must not be null and must be prefixed with "Bearer" plus a whitespace + // The authentication scheme comparison must be case-insensitive + return authorizationHeader != null && authorizationHeader.toLowerCase().startsWith(AUTHENTICATION_TOKEN_SCHEME.toLowerCase() + " "); + } + private void abortWithUnauthorized(ContainerRequestContext requestContext) { @@ -181,7 +195,12 @@ public class AuthenticationFilter implements ContainerRequestFilter { .build()); } - private User validateToken(String authorization) throws Exception { + protected UserByToken validateToken(String authorization) throws Exception { + System.out.println("dsfgsdgsdfgsdgf"); + return null; + } + // must be override to be good implemenres + protected User validateJwtToken(String authorization) throws Exception { System.out.println(" validate token : " + authorization); JWTClaimsSet ret = JWTWrapper.validateToken(authorization, "KarAuth", null); // check the token is valid !!! (signed and coherent issuer... diff --git a/src/org/kar/archidata/filter/GenericContext.java b/src/org/kar/archidata/filter/GenericContext.java index faeb5c9..a45c19b 100644 --- a/src/org/kar/archidata/filter/GenericContext.java +++ b/src/org/kar/archidata/filter/GenericContext.java @@ -1,15 +1,18 @@ package org.kar.archidata.filter; import org.kar.archidata.model.User; +import org.kar.archidata.model.UserByToken; import java.security.Principal; public class GenericContext implements Principal { public User user; + public UserByToken userByToken; - public GenericContext(User user) { + public GenericContext(User user, UserByToken userByToken) { this.user = user; + this.userByToken = userByToken; } @Override diff --git a/src/org/kar/archidata/filter/MySecurityContext.java b/src/org/kar/archidata/filter/MySecurityContext.java index b61a564..7dcdcb2 100644 --- a/src/org/kar/archidata/filter/MySecurityContext.java +++ b/src/org/kar/archidata/filter/MySecurityContext.java @@ -2,6 +2,7 @@ package org.kar.archidata.filter; import org.kar.archidata.model.User; +import org.kar.archidata.model.UserByToken; import javax.ws.rs.core.SecurityContext; import java.security.Principal; @@ -12,8 +13,8 @@ class MySecurityContext implements SecurityContext { private final GenericContext contextPrincipale; private final String sheme; - public MySecurityContext(User user, String sheme) { - this.contextPrincipale = new GenericContext(user); + public MySecurityContext(User user, UserByToken userByToken, String sheme) { + this.contextPrincipale = new GenericContext(user, userByToken); this.sheme = sheme; } @@ -24,24 +25,36 @@ class MySecurityContext implements SecurityContext { @Override public boolean isUserInRole(String role) { - if (role.contentEquals("ADMIN")) { - return contextPrincipale.user.admin == true; - } - if (role.contentEquals("USER")) { - // if not an admin, this is a user... - return true; //contextPrincipale.user.admin == false; + if (contextPrincipale.user != null) { + if (role.contentEquals("ADMIN")) { + return contextPrincipale.user.admin == true; + } + if (role.contentEquals("USER")) { + // if not an admin, this is a user... + return true; //contextPrincipale.user.admin == false; + } } + if (contextPrincipale.userByToken != null) { + Boolean value = this.contextPrincipale.userByToken.right.get(role); + return value == true; + } return false; } @Override public boolean isSecure() { - return true; + return sheme.equalsIgnoreCase("https"); } @Override public String getAuthenticationScheme() { - return "Yota"; + if (contextPrincipale.user != null) { + return "Yota"; + } + if (contextPrincipale.userByToken != null) { + return "Zota"; + } + return null; } } \ No newline at end of file diff --git a/src/org/kar/archidata/model/GenericTable.java b/src/org/kar/archidata/model/GenericTable.java index 41ab29c..e4b1e19 100644 --- a/src/org/kar/archidata/model/GenericTable.java +++ b/src/org/kar/archidata/model/GenericTable.java @@ -26,12 +26,12 @@ public class GenericTable { @SQLCreateTime @SQLNotNull @SQLComment("Create time of the object") - @SQLDefault("now()") + @SQLDefault("now(3)") public Timestamp create_date = null; @SQLNotRead @SQLUpdateTime @SQLNotNull @SQLComment("When update the object") - @SQLDefault("now()") + @SQLDefault("now(3)") public Timestamp modify_date = null; } diff --git a/src/org/kar/archidata/model/GenericToken.java b/src/org/kar/archidata/model/GenericToken.java new file mode 100644 index 0000000..5f6195d --- /dev/null +++ b/src/org/kar/archidata/model/GenericToken.java @@ -0,0 +1,23 @@ +package org.kar.archidata.model; + +import java.sql.Timestamp; + +import org.kar.archidata.annotation.SQLIfNotExists; +import org.kar.archidata.annotation.SQLNotNull; +import org.kar.archidata.annotation.SQLTableName; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@SQLTableName ("applicationToken") +@SQLIfNotExists +@JsonInclude(JsonInclude.Include.NON_NULL) +public class GenericToken extends GenericTable { + @SQLNotNull + public Long parentId; + @SQLNotNull + public String name; + @SQLNotNull + public Timestamp endValidityTime = null; + @SQLNotNull + public String token; +} diff --git a/src/org/kar/archidata/model/Token.java b/src/org/kar/archidata/model/Token.java index 636cbfd..61f1ae6 100644 --- a/src/org/kar/archidata/model/Token.java +++ b/src/org/kar/archidata/model/Token.java @@ -3,16 +3,7 @@ package org.kar.archidata.model; import java.sql.ResultSet; import java.sql.SQLException; -/* -CREATE TABLE `token` ( - `id` bigint NOT NULL COMMENT 'Unique ID of the TOKEN' AUTO_INCREMENT PRIMARY KEY, - `userId` bigint NOT NULL COMMENT 'Unique ID of the user', - `token` varchar(128) COLLATE 'latin1_bin' NOT NULL COMMENT 'Token (can be not unique)', - `createTime` datetime NOT NULL COMMENT 'Time the token has been created', - `endValidityTime` datetime NOT NULL COMMENT 'Time of the token end validity' -) AUTO_INCREMENT=10; - */ public class Token { public Long id; public Long userId; diff --git a/src/org/kar/archidata/model/UserByToken.java b/src/org/kar/archidata/model/UserByToken.java new file mode 100644 index 0000000..d2abc6a --- /dev/null +++ b/src/org/kar/archidata/model/UserByToken.java @@ -0,0 +1,19 @@ +package org.kar.archidata.model; + +import java.util.HashMap; +import java.util.Map; + + +public class UserByToken { + public static final int TYPE_USER = -1; + public static final int TYPE_APPLICATION = -2; + // application internal management type: an application generic Id + public Integer type = null; + + public Long id = null; + public Long parentId = null; + public String name = null; + // Right map + public Map right = new HashMap<>(); + +} diff --git a/src/org/kar/archidata/util/ConfigBaseVariable.java b/src/org/kar/archidata/util/ConfigBaseVariable.java index 6501e17..4ce11b1 100644 --- a/src/org/kar/archidata/util/ConfigBaseVariable.java +++ b/src/org/kar/archidata/util/ConfigBaseVariable.java @@ -10,6 +10,7 @@ public class ConfigBaseVariable { static public String bdDatabase = System.getenv("DB_DATABASE"); static public String apiAdress = System.getenv("API_ADDRESS"); static public String ssoAdress = System.getenv("SSO_ADDRESS"); + static public String ssoToken = System.getenv("SSO_TOKEN"); public static String getTmpDataFolder() { if (tmpDataFolder == null) { @@ -74,4 +75,7 @@ public class ConfigBaseVariable { } return ssoAdress; } + public static String ssoToken() { + return ssoToken; + } } diff --git a/src/org/kar/archidata/util/JWTWrapper.java b/src/org/kar/archidata/util/JWTWrapper.java index 2b9452e..d941cf7 100644 --- a/src/org/kar/archidata/util/JWTWrapper.java +++ b/src/org/kar/archidata/util/JWTWrapper.java @@ -46,6 +46,10 @@ public class JWTWrapper { con.setRequestProperty("Cache-Control", "no-cache"); con.setRequestProperty("Content-Type", "application/json"); con.setRequestProperty("Accept", "application/json"); + String ssoToken = ConfigBaseVariable.ssoToken(); + if (ssoToken != null) { + con.setRequestProperty("Authorization", "Zota " + ssoToken); + } int responseCode = con.getResponseCode(); System.out.println("GET Response Code :: " + responseCode); @@ -95,12 +99,19 @@ public class JWTWrapper { } } - public static String getPublicKey() { + public static String getPublicKeyJson() { if (rsaPublicJWK == null) { return null; } return rsaPublicJWK.toJSONString(); } + public static java.security.interfaces.RSAPublicKey getPublicKeyJava() throws JOSEException { + if (rsaPublicJWK == null) { + return null; + } + // Convert back to std Java interface + return rsaPublicJWK.toRSAPublicKey(); + } /** * Create a token with the provided elements