From c0ab443c436808e3423b78f076ed6a026b9d9e8f Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Sat, 28 Jan 2023 00:17:07 +0100 Subject: [PATCH] [DEV] add a secure way to access on the public kay with application token --- back/pom.xml | 2 +- back/src/org/kar/karso/WebLauncher.java | 5 +- .../kar/karso/api/ApplicationResource.java | 20 +- .../karso/api/ApplicationTokenResource.java | 171 ++++++++++++++++++ back/src/org/kar/karso/api/Front.java | 2 +- .../org/kar/karso/api/PublicKeyResource.java | 56 +++--- back/src/org/kar/karso/api/UserResource.java | 2 +- .../filter/KarsoAuthenticationFilter.java | 63 +++++++ .../org/kar/karso/model/ApplicationToken.java | 14 ++ 9 files changed, 282 insertions(+), 53 deletions(-) create mode 100755 back/src/org/kar/karso/api/ApplicationTokenResource.java create mode 100644 back/src/org/kar/karso/filter/KarsoAuthenticationFilter.java create mode 100644 back/src/org/kar/karso/model/ApplicationToken.java diff --git a/back/pom.xml b/back/pom.xml index d3a3487..9cbf959 100644 --- a/back/pom.xml +++ b/back/pom.xml @@ -27,7 +27,7 @@ kangaroo-and-rabbit archidata - 0.2.4 + 0.3.1 diff --git a/back/src/org/kar/karso/WebLauncher.java b/back/src/org/kar/karso/WebLauncher.java index de2e1e4..11a5a2e 100755 --- a/back/src/org/kar/karso/WebLauncher.java +++ b/back/src/org/kar/karso/WebLauncher.java @@ -9,7 +9,9 @@ import org.kar.karso.api.HealthCheck; import org.kar.karso.api.PublicKeyResource; import org.kar.karso.api.SystemConfigResource; import org.kar.karso.api.UserResource; +import org.kar.karso.filter.KarsoAuthenticationFilter; import org.kar.karso.model.Application; +import org.kar.karso.model.ApplicationToken; import org.kar.karso.model.Settings; import org.kar.karso.model.UserAuth; //import org.kar.archidata.model.Migration; @@ -55,6 +57,7 @@ public class WebLauncher { out += SqlWrapper.createTable(Settings.class); out += SqlWrapper.createTable(UserAuth.class); out += SqlWrapper.createTable(Application.class); + out += SqlWrapper.createTable(ApplicationToken.class); //out += SqlWrapper.createTable(Migration.class); System.out.println(out); } catch (Exception e) { @@ -69,7 +72,7 @@ public class WebLauncher { rc.register(new OptionFilter()); // remove cors ==> all time called by an other system... rc.register(new CORSFilter()); - rc.registerClasses(AuthenticationFilter.class); + rc.registerClasses(KarsoAuthenticationFilter.class); // register exception catcher rc.register(InputExceptionCatcher.class); rc.register(SystemExceptionCatcher.class); diff --git a/back/src/org/kar/karso/api/ApplicationResource.java b/back/src/org/kar/karso/api/ApplicationResource.java index ef2d629..bdf34de 100755 --- a/back/src/org/kar/karso/api/ApplicationResource.java +++ b/back/src/org/kar/karso/api/ApplicationResource.java @@ -26,28 +26,16 @@ public class ApplicationResource { @GET @RolesAllowed(value= {"USER", "ADMIN"}) - public List getApplications() { + public List getApplications() throws Exception { System.out.println("getApplications"); - try { - return SqlWrapper.gets(Application.class, false); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return null; + return SqlWrapper.gets(Application.class, false); } @GET @Path("small") @RolesAllowed(value= {"USER", "ADMIN"}) - public List getApplicationsSmall() { + public List getApplicationsSmall() throws Exception { System.out.println("getApplications"); - try { - return SqlWrapper.gets(ApplicationSmall.class, false); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return null; + return SqlWrapper.gets(ApplicationSmall.class, false); } diff --git a/back/src/org/kar/karso/api/ApplicationTokenResource.java b/back/src/org/kar/karso/api/ApplicationTokenResource.java new file mode 100755 index 0000000..4aa67ef --- /dev/null +++ b/back/src/org/kar/karso/api/ApplicationTokenResource.java @@ -0,0 +1,171 @@ +package org.kar.karso.api; + +import org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.kar.archidata.SqlWrapper; +import org.kar.archidata.WhereCondition; +import org.kar.archidata.filter.GenericContext; +import org.kar.archidata.model.Data; +import org.kar.karso.model.*; +import org.kar.archidata.util.JWTWrapper; +import org.kar.archidata.annotation.security.PermitAll; +import org.kar.archidata.annotation.security.RolesAllowed; +import org.kar.archidata.api.DataResource; +import org.kar.archidata.exception.InputException; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.List; + +import javax.ws.rs.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; + + + +@Path("/application_token") +@Produces( MediaType.APPLICATION_JSON) +public class ApplicationTokenResource { + + public ApplicationTokenResource() { + } + + @GET + @Path("{applicationId}") + @RolesAllowed(value= {"ADMIN"}) + public List gets(@Context SecurityContext sc, @PathParam("applicationId") Long applicationId) throws Exception { + List values = SqlWrapper.getsWhere(ApplicationToken.class, + List.of( + new WhereCondition("parentId", "=", applicationId) + ), + false); + // clean all tokens this is a secret: + for (ApplicationToken elem : values) { + elem.token = null; + } + return values; + } + @DELETE + @Path("{applicationId}/{tokenId}") + @RolesAllowed(value= {"ADMIN"}) + public Response delete( + @Context SecurityContext sc, + @PathParam("applicationId") Long applicationId, + @PathParam("tokenId") Integer tokenId) throws Exception { + int nbRemoved = SqlWrapper.setDeleteWhere(ApplicationToken.class, + List.of( + new WhereCondition("parentId", "=", applicationId), + new WhereCondition("tokenId", "=", tokenId) + ) + ); + if (nbRemoved == 0) { + return Response.notModified("{}").build(); + } + if (nbRemoved == 0) { + return Response.serverError().build(); + } + return Response.ok("{}").build(); + } + + private String multipartCorrection(String data) { + if (data == null) { + return null; + } + if (data.isEmpty()) { + return null; + } + if (data.contentEquals("null")) { + return null; + } + return data; + } + + static String randomToken() { + int len = 48; + String valid_element = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvxyz0123456789#_@-|[])('~$%*/\\.!?,"; + // creating a StringBuffer size of AlphaNumericStr + StringBuilder out = new StringBuilder(len); + int iii; + for (iii=0; iii maximum) { + validity = maximum; + } + // todo: set validity timestamp ... + ApplicationToken token = new ApplicationToken(); + token.token = randomToken(); + token.name = multipartCorrection(name); + token.parentId = applicationId; + OffsetDateTime now = OffsetDateTime.now( ZoneOffset.UTC ); + token.endValidityTime = Timestamp.from(now.plusDays(validity).toInstant()); + + // insert in the BDD + token = SqlWrapper.insert(token); + // here we return the token to permit to the user to see it to set it in the application. + return token; + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/back/src/org/kar/karso/api/Front.java b/back/src/org/kar/karso/api/Front.java index 0179afb..bc80c0c 100644 --- a/back/src/org/kar/karso/api/Front.java +++ b/back/src/org/kar/karso/api/Front.java @@ -5,7 +5,7 @@ import javax.ws.rs.*; import org.kar.archidata.api.FrontGeneric; import org.kar.karso.util.ConfigVariable; -@Path("/karso") +@Path("/front") public class Front extends FrontGeneric { public Front() { this.baseFrontFolder = ConfigVariable.getFrontFolder(); diff --git a/back/src/org/kar/karso/api/PublicKeyResource.java b/back/src/org/kar/karso/api/PublicKeyResource.java index 2031aa7..531758d 100755 --- a/back/src/org/kar/karso/api/PublicKeyResource.java +++ b/back/src/org/kar/karso/api/PublicKeyResource.java @@ -3,53 +3,43 @@ package org.kar.karso.api; import org.kar.archidata.util.JWTWrapper; import org.kar.archidata.util.JWTWrapper.PublicKey; +import com.nimbusds.jose.JOSEException; + import org.kar.archidata.annotation.security.PermitAll; +import org.kar.archidata.annotation.security.RolesAllowed; + +import java.security.interfaces.RSAPublicKey; +import java.util.Base64; + import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @Path("/public_key") @Produces(MediaType.APPLICATION_JSON) public class PublicKeyResource { - + public PublicKeyResource() { } + // This is for java server that use the same implementation // curl http://localhost:9993/public_key @GET @PermitAll + @RolesAllowed(value= {"APPLICATION"}) public PublicKey getKey() { - return new PublicKey(JWTWrapper.getPublicKey()); + return new PublicKey(JWTWrapper.getPublicKeyJson()); + } + + // This is for common other interface that support public PEM stream. + // curl http://localhost:9993/public_key/pem + @GET + @Path("/pem") + @RolesAllowed(value= {"APPLICATION"}) + public String getKeyPem() throws JOSEException { + RSAPublicKey keyPub = JWTWrapper.getPublicKeyJava(); + byte[] data = keyPub.getEncoded(); + String base64encoded = new String(Base64.getEncoder().encode(data)); + return "-----BEGIN PUBLIC KEY-----\n" + base64encoded + "\n-----END PUBLIC KEY-----\n"; } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/back/src/org/kar/karso/api/UserResource.java b/back/src/org/kar/karso/api/UserResource.java index bb45bd9..dc96802 100755 --- a/back/src/org/kar/karso/api/UserResource.java +++ b/back/src/org/kar/karso/api/UserResource.java @@ -68,7 +68,7 @@ public class UserResource { user.blocked = data; int ret = SqlWrapper.update(user, userId, List.of("blocked")); if (ret == 0) { - return Response.notModified("{}").build(); + return Response.notModified("{}").build(); } return Response.ok("{}").build(); } diff --git a/back/src/org/kar/karso/filter/KarsoAuthenticationFilter.java b/back/src/org/kar/karso/filter/KarsoAuthenticationFilter.java new file mode 100644 index 0000000..fa9e202 --- /dev/null +++ b/back/src/org/kar/karso/filter/KarsoAuthenticationFilter.java @@ -0,0 +1,63 @@ +package org.kar.karso.filter; + +import java.sql.Timestamp; +import java.time.Instant; + +import org.kar.archidata.filter.AuthenticationFilter; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.ext.Provider; + +import org.kar.archidata.SqlWrapper; +import org.kar.archidata.model.UserByToken; +import org.kar.karso.model.ApplicationToken; + +//@PreMatching +@Provider +@Priority(Priorities.AUTHENTICATION) +public class KarsoAuthenticationFilter extends AuthenticationFilter { + + //curl http://0.0.0.0:15080/karso/api/public_key/pem --output plop.txt -H "Authorization: Zota 1:U0sJM1m@-STSdfg4365fJOFUGbR4kFycBu1qGZPwf7gW6k2WWRBzTPUH7QutCgPw-SDss45_563sSDFdfg@dsf@456" --verbose + + @Override + protected UserByToken validateToken(String authorization) throws Exception { + if (authorization == null || authorization.length() < 25) { + System.out.println("Application authentication too short '" + authorization + "'"); + return null; + } + String[] elems = authorization.split(":"); + if (elems.length != 2) { + System.out.println("Application authentication split error '" + authorization + "'"); + return null; + } + Long indexToken = Long.parseLong(elems[0]); + + ApplicationToken value = SqlWrapper.get(ApplicationToken.class, indexToken); + if (value == null) { + System.out.println("Application authentication can not find id '" + authorization + "'"); + return null; + } + // TODO: hash the sent token + if (!value.token.equals(elems[1])) { + System.out.println("Application authentication mismatch '" + authorization + "'"); + return null; + } + // TODO: check UTC !!! + if (!value.endValidityTime.after(Timestamp.from(Instant.now()))) { + System.out.println("Application authentication Time-out '" + authorization + "' " + value.endValidityTime + " > " + Timestamp.from(Instant.now())); + return null; + } + // ---------------------------------- + // -- All is good !!! + // ---------------------------------- + // We are in transition phase the user element will be removed + UserByToken userByToken = new UserByToken(); + userByToken.id = value.id; + userByToken.name = value.name; + userByToken.parentId = value.parentId; + userByToken.type = UserByToken.TYPE_APPLICATION; + userByToken.right.put("APPLICATION", true); + return userByToken; + } +} diff --git a/back/src/org/kar/karso/model/ApplicationToken.java b/back/src/org/kar/karso/model/ApplicationToken.java new file mode 100644 index 0000000..35bc343 --- /dev/null +++ b/back/src/org/kar/karso/model/ApplicationToken.java @@ -0,0 +1,14 @@ +package org.kar.karso.model; + +import org.kar.archidata.annotation.SQLIfNotExists; +import org.kar.archidata.annotation.SQLTableName; +import org.kar.archidata.model.GenericToken; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@SQLTableName ("applicationToken") +@SQLIfNotExists +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ApplicationToken extends GenericToken { + +} \ No newline at end of file