[DEV] add a secure way to access on the public kay with application token
This commit is contained in:
parent
ede555cb30
commit
c0ab443c43
@ -27,7 +27,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>kangaroo-and-rabbit</groupId>
|
<groupId>kangaroo-and-rabbit</groupId>
|
||||||
<artifactId>archidata</artifactId>
|
<artifactId>archidata</artifactId>
|
||||||
<version>0.2.4</version>
|
<version>0.3.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- testing -->
|
<!-- testing -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -9,7 +9,9 @@ import org.kar.karso.api.HealthCheck;
|
|||||||
import org.kar.karso.api.PublicKeyResource;
|
import org.kar.karso.api.PublicKeyResource;
|
||||||
import org.kar.karso.api.SystemConfigResource;
|
import org.kar.karso.api.SystemConfigResource;
|
||||||
import org.kar.karso.api.UserResource;
|
import org.kar.karso.api.UserResource;
|
||||||
|
import org.kar.karso.filter.KarsoAuthenticationFilter;
|
||||||
import org.kar.karso.model.Application;
|
import org.kar.karso.model.Application;
|
||||||
|
import org.kar.karso.model.ApplicationToken;
|
||||||
import org.kar.karso.model.Settings;
|
import org.kar.karso.model.Settings;
|
||||||
import org.kar.karso.model.UserAuth;
|
import org.kar.karso.model.UserAuth;
|
||||||
//import org.kar.archidata.model.Migration;
|
//import org.kar.archidata.model.Migration;
|
||||||
@ -55,6 +57,7 @@ public class WebLauncher {
|
|||||||
out += SqlWrapper.createTable(Settings.class);
|
out += SqlWrapper.createTable(Settings.class);
|
||||||
out += SqlWrapper.createTable(UserAuth.class);
|
out += SqlWrapper.createTable(UserAuth.class);
|
||||||
out += SqlWrapper.createTable(Application.class);
|
out += SqlWrapper.createTable(Application.class);
|
||||||
|
out += SqlWrapper.createTable(ApplicationToken.class);
|
||||||
//out += SqlWrapper.createTable(Migration.class);
|
//out += SqlWrapper.createTable(Migration.class);
|
||||||
System.out.println(out);
|
System.out.println(out);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -69,7 +72,7 @@ public class WebLauncher {
|
|||||||
rc.register(new OptionFilter());
|
rc.register(new OptionFilter());
|
||||||
// remove cors ==> all time called by an other system...
|
// remove cors ==> all time called by an other system...
|
||||||
rc.register(new CORSFilter());
|
rc.register(new CORSFilter());
|
||||||
rc.registerClasses(AuthenticationFilter.class);
|
rc.registerClasses(KarsoAuthenticationFilter.class);
|
||||||
// register exception catcher
|
// register exception catcher
|
||||||
rc.register(InputExceptionCatcher.class);
|
rc.register(InputExceptionCatcher.class);
|
||||||
rc.register(SystemExceptionCatcher.class);
|
rc.register(SystemExceptionCatcher.class);
|
||||||
|
@ -26,28 +26,16 @@ public class ApplicationResource {
|
|||||||
|
|
||||||
@GET
|
@GET
|
||||||
@RolesAllowed(value= {"USER", "ADMIN"})
|
@RolesAllowed(value= {"USER", "ADMIN"})
|
||||||
public List<Application> getApplications() {
|
public List<Application> getApplications() throws Exception {
|
||||||
System.out.println("getApplications");
|
System.out.println("getApplications");
|
||||||
try {
|
return SqlWrapper.gets(Application.class, false);
|
||||||
return SqlWrapper.gets(Application.class, false);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
@GET
|
@GET
|
||||||
@Path("small")
|
@Path("small")
|
||||||
@RolesAllowed(value= {"USER", "ADMIN"})
|
@RolesAllowed(value= {"USER", "ADMIN"})
|
||||||
public List<ApplicationSmall> getApplicationsSmall() {
|
public List<ApplicationSmall> getApplicationsSmall() throws Exception {
|
||||||
System.out.println("getApplications");
|
System.out.println("getApplications");
|
||||||
try {
|
return SqlWrapper.gets(ApplicationSmall.class, false);
|
||||||
return SqlWrapper.gets(ApplicationSmall.class, false);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
171
back/src/org/kar/karso/api/ApplicationTokenResource.java
Executable file
171
back/src/org/kar/karso/api/ApplicationTokenResource.java
Executable file
@ -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<ApplicationToken> gets(@Context SecurityContext sc, @PathParam("applicationId") Long applicationId) throws Exception {
|
||||||
|
List<ApplicationToken> 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<len; iii++) {
|
||||||
|
//generating a random number using math.random()
|
||||||
|
int ch = (int)(valid_element.length() * Math.random());
|
||||||
|
//adding Random character one by one at the end of s
|
||||||
|
out.append(valid_element.charAt(ch));
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{applicationId}/create")
|
||||||
|
@RolesAllowed("ADMIN")
|
||||||
|
public ApplicationToken createToken(
|
||||||
|
@Context SecurityContext sc,
|
||||||
|
@PathParam("applicationId") Long applicationId,
|
||||||
|
@FormDataParam("name") String name,
|
||||||
|
@FormDataParam("validity") Integer validity
|
||||||
|
) throws Exception {
|
||||||
|
// correct input string stream :
|
||||||
|
name = multipartCorrection(name);
|
||||||
|
//validity = multipartCorrection(validity);
|
||||||
|
|
||||||
|
if (applicationId == null) {
|
||||||
|
throw new InputException("applicationId", "can not be null");
|
||||||
|
}
|
||||||
|
int maximum = 365*5;
|
||||||
|
if (validity == null || validity < 0 || validity > 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -5,7 +5,7 @@ import javax.ws.rs.*;
|
|||||||
import org.kar.archidata.api.FrontGeneric;
|
import org.kar.archidata.api.FrontGeneric;
|
||||||
import org.kar.karso.util.ConfigVariable;
|
import org.kar.karso.util.ConfigVariable;
|
||||||
|
|
||||||
@Path("/karso")
|
@Path("/front")
|
||||||
public class Front extends FrontGeneric {
|
public class Front extends FrontGeneric {
|
||||||
public Front() {
|
public Front() {
|
||||||
this.baseFrontFolder = ConfigVariable.getFrontFolder();
|
this.baseFrontFolder = ConfigVariable.getFrontFolder();
|
||||||
|
@ -3,53 +3,43 @@ package org.kar.karso.api;
|
|||||||
import org.kar.archidata.util.JWTWrapper;
|
import org.kar.archidata.util.JWTWrapper;
|
||||||
import org.kar.archidata.util.JWTWrapper.PublicKey;
|
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.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.*;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
@Path("/public_key")
|
@Path("/public_key")
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public class PublicKeyResource {
|
public class PublicKeyResource {
|
||||||
|
|
||||||
public PublicKeyResource() {
|
public PublicKeyResource() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// This is for java server that use the same implementation
|
||||||
// curl http://localhost:9993/public_key
|
// curl http://localhost:9993/public_key
|
||||||
@GET
|
@GET
|
||||||
@PermitAll
|
@PermitAll
|
||||||
|
@RolesAllowed(value= {"APPLICATION"})
|
||||||
public PublicKey getKey() {
|
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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ public class UserResource {
|
|||||||
user.blocked = data;
|
user.blocked = data;
|
||||||
int ret = SqlWrapper.update(user, userId, List.of("blocked"));
|
int ret = SqlWrapper.update(user, userId, List.of("blocked"));
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
return Response.notModified("{}").build();
|
return Response.notModified("{}").build();
|
||||||
}
|
}
|
||||||
return Response.ok("{}").build();
|
return Response.ok("{}").build();
|
||||||
}
|
}
|
||||||
|
63
back/src/org/kar/karso/filter/KarsoAuthenticationFilter.java
Normal file
63
back/src/org/kar/karso/filter/KarsoAuthenticationFilter.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
14
back/src/org/kar/karso/model/ApplicationToken.java
Normal file
14
back/src/org/kar/karso/model/ApplicationToken.java
Normal file
@ -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 {
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user