[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>
|
||||
<groupId>kangaroo-and-rabbit</groupId>
|
||||
<artifactId>archidata</artifactId>
|
||||
<version>0.2.4</version>
|
||||
<version>0.3.1</version>
|
||||
</dependency>
|
||||
<!-- testing -->
|
||||
<dependency>
|
||||
|
@ -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);
|
||||
|
@ -26,28 +26,16 @@ public class ApplicationResource {
|
||||
|
||||
@GET
|
||||
@RolesAllowed(value= {"USER", "ADMIN"})
|
||||
public List<Application> getApplications() {
|
||||
public List<Application> 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<ApplicationSmall> getApplicationsSmall() {
|
||||
public List<ApplicationSmall> 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);
|
||||
}
|
||||
|
||||
|
||||
|
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.karso.util.ConfigVariable;
|
||||
|
||||
@Path("/karso")
|
||||
@Path("/front")
|
||||
public class Front extends FrontGeneric {
|
||||
public Front() {
|
||||
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.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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
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…
Reference in New Issue
Block a user