- missing some @Produces - update logger in static - some coding style - Some error in code
349 lines
12 KiB
Java
Executable File
349 lines
12 KiB
Java
Executable File
package org.kar.karso.api;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.sql.Timestamp;
|
|
import java.time.LocalDateTime;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import org.kar.archidata.dataAccess.DataAccess;
|
|
import org.kar.archidata.dataAccess.QueryCondition;
|
|
import org.kar.archidata.dataAccess.addOn.AddOnManyToMany;
|
|
import org.kar.archidata.dataAccess.options.Condition;
|
|
import org.kar.archidata.exception.FailException;
|
|
import org.kar.archidata.exception.InputException;
|
|
import org.kar.archidata.exception.SystemException;
|
|
import org.kar.archidata.filter.GenericContext;
|
|
import org.kar.archidata.model.GetToken;
|
|
import org.kar.archidata.tools.JWTWrapper;
|
|
import org.kar.karso.migration.Initialization;
|
|
import org.kar.karso.model.ChangePassword;
|
|
import org.kar.karso.model.DataGetToken;
|
|
import org.kar.karso.model.UserAuth;
|
|
import org.kar.karso.model.UserAuthGet;
|
|
import org.kar.karso.model.UserCreate;
|
|
import org.kar.karso.util.ConfigVariable;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
|
|
|
import jakarta.annotation.security.PermitAll;
|
|
import jakarta.annotation.security.RolesAllowed;
|
|
import jakarta.ws.rs.Consumes;
|
|
import jakarta.ws.rs.GET;
|
|
import jakarta.ws.rs.PATCH;
|
|
import jakarta.ws.rs.POST;
|
|
import jakarta.ws.rs.Path;
|
|
import jakarta.ws.rs.PathParam;
|
|
import jakarta.ws.rs.Produces;
|
|
import jakarta.ws.rs.QueryParam;
|
|
import jakarta.ws.rs.core.Context;
|
|
import jakarta.ws.rs.core.MediaType;
|
|
import jakarta.ws.rs.core.Response;
|
|
import jakarta.ws.rs.core.SecurityContext;
|
|
|
|
@Path("/users")
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
public class UserResource {
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class);
|
|
|
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
|
public class UserOut {
|
|
public long id;
|
|
public String login;
|
|
|
|
public UserOut(final long id, final String login) {
|
|
this.id = id;
|
|
this.login = login;
|
|
}
|
|
}
|
|
|
|
public UserResource() {}
|
|
|
|
@GET
|
|
@RolesAllowed("ADMIN")
|
|
public List<UserAuthGet> getUsers() throws Exception {
|
|
return DataAccess.gets(UserAuthGet.class);
|
|
}
|
|
|
|
@GET
|
|
@Path("{id}")
|
|
@RolesAllowed("ADMIN")
|
|
public UserAuthGet getUser(@Context final SecurityContext sc, @PathParam("id") final long userId) throws Exception {
|
|
//GenericContext gc = (GenericContext) sc.getUserPrincipal();
|
|
return DataAccess.get(UserAuthGet.class, userId);
|
|
}
|
|
|
|
@POST
|
|
@Path("{userId}/application/{applicationId}/link")
|
|
@RolesAllowed("ADMIN")
|
|
public UserAuth linkApplication(
|
|
@Context final SecurityContext sc,
|
|
@PathParam("userId") final long userId,
|
|
@PathParam("applicationId") final long applicationId,
|
|
final boolean data) throws Exception {
|
|
LOGGER.debug("Find typeNode");
|
|
if (data) {
|
|
AddOnManyToMany.addLink(UserAuth.class, userId, "application", applicationId);
|
|
} else {
|
|
AddOnManyToMany.removeLink(UserAuth.class, userId, "application", applicationId);
|
|
}
|
|
return DataAccess.get(UserAuth.class, userId);
|
|
}
|
|
|
|
@GET
|
|
@Path("{userId}/application/{applicationId}/rights")
|
|
@RolesAllowed("ADMIN")
|
|
public Map<String, Object> getApplicationRight(
|
|
@Context final SecurityContext sc,
|
|
@PathParam("userId") final long userId,
|
|
@PathParam("applicationId") final long applicationId) throws Exception {
|
|
return RightResource.getUserRight(userId, applicationId);
|
|
}
|
|
|
|
@PATCH
|
|
@Path("{userId}/application/{applicationId}/rights")
|
|
@RolesAllowed("ADMIN")
|
|
@Consumes(MediaType.APPLICATION_JSON)
|
|
public Map<String, Object> patchApplicationRight(
|
|
@Context final SecurityContext sc,
|
|
@PathParam("userId") final long userId,
|
|
@PathParam("applicationId") final long applicationId,
|
|
final Map<String, Object> data) throws Exception {
|
|
this.LOGGER.info("Patch data from FRONT: {}", data);
|
|
RightResource.updateUserRight(userId, applicationId, data);
|
|
return RightResource.getUserRight(userId, applicationId);
|
|
}
|
|
|
|
// TODO: check this it might be deprecated ...
|
|
@POST
|
|
@Path("{id}/set_admin")
|
|
@RolesAllowed("ADMIN")
|
|
@Consumes(MediaType.APPLICATION_JSON)
|
|
public void setAdmin(@Context final SecurityContext sc, @PathParam("id") final long userId, final boolean data)
|
|
throws Exception {
|
|
final UserAuth user = new UserAuth();
|
|
user.admin = data;
|
|
final int ret = DataAccess.update(user, userId, List.of("admin"));
|
|
if (ret == 0) {
|
|
throw new FailException(Response.Status.NOT_MODIFIED, "Fail to modify user as an admin.");
|
|
}
|
|
}
|
|
|
|
@POST
|
|
@Path("{id}/set_blocked")
|
|
@RolesAllowed("ADMIN")
|
|
@Consumes(MediaType.APPLICATION_JSON)
|
|
public void setBlocked(@Context final SecurityContext sc, @PathParam("id") final long userId, final boolean data)
|
|
throws Exception {
|
|
final UserAuth user = new UserAuth();
|
|
user.blocked = data;
|
|
final int ret = DataAccess.update(user, userId, List.of("blocked"));
|
|
if (ret == 0) {
|
|
throw new FailException(Response.Status.NOT_MODIFIED, "Fail to block the User.");
|
|
}
|
|
}
|
|
|
|
@POST
|
|
@RolesAllowed("ADMIN")
|
|
@Consumes(MediaType.APPLICATION_JSON)
|
|
public UserAuthGet create(final UserCreate user) throws Exception {
|
|
LOGGER.debug("create new User email={} login={}", user.email, user.login);
|
|
// verify login or email is correct:
|
|
if (user.login == null || user.login.length() < 6) {
|
|
throw new InputException("login", "Authentiocate-method-error (login too small: '" + user.login + "')");
|
|
}
|
|
// TODO: check login format
|
|
|
|
if (user.email == null || user.email.length() < 6) {
|
|
throw new InputException("email", "Authentiocate-method-error (email too small: '" + user.email + "')");
|
|
}
|
|
// TODO: check email format
|
|
|
|
if (user.password == null || user.password.length() != 128) {
|
|
throw new InputException("password", "null password, or wrong hash size");
|
|
}
|
|
// TODO: verify if the data are a hash ...
|
|
|
|
// Check login does not exist
|
|
List<UserAuth> out = DataAccess.getsWhere(UserAuth.class,
|
|
new Condition(new QueryCondition("login", "=", user.login)));
|
|
if (out.size() >= 1) {
|
|
throw new FailException(Response.Status.BAD_REQUEST, "Login already used !!!");
|
|
}
|
|
// Check email does not exist
|
|
out = DataAccess.getsWhere(UserAuth.class, new Condition(new QueryCondition("email", "=", user.email)));
|
|
if (out.size() >= 1) {
|
|
throw new FailException(Response.Status.BAD_REQUEST, "e-mail already used !!!");
|
|
}
|
|
|
|
// Add new user and return formated data.
|
|
final UserAuth newUser = new UserAuth();
|
|
newUser.admin = false;
|
|
newUser.removed = false;
|
|
newUser.blocked = false;
|
|
newUser.avatar = false;
|
|
newUser.login = user.login;
|
|
newUser.password = user.password;
|
|
newUser.email = user.email;
|
|
newUser.lastConnection = Timestamp.valueOf(LocalDateTime.now());
|
|
final UserAuth tmp = DataAccess.insert(newUser);
|
|
LOGGER.debug("create new user done with id=={}", tmp.id);
|
|
return DataAccess.get(UserAuthGet.class, tmp.id);
|
|
}
|
|
|
|
@GET
|
|
@Path("me")
|
|
@RolesAllowed("USER")
|
|
public UserOut getMe(@Context final SecurityContext sc) {
|
|
LOGGER.debug("getMe()");
|
|
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
|
|
LOGGER.debug("== USER ? {}", gc.userByToken);
|
|
return new UserOut(gc.userByToken.id, gc.userByToken.name);
|
|
}
|
|
|
|
@POST
|
|
@Path("password")
|
|
@RolesAllowed("USER")
|
|
@Consumes(MediaType.APPLICATION_JSON)
|
|
public void changePassword(@Context final SecurityContext sc, final ChangePassword data) throws Exception {
|
|
LOGGER.debug("ChangePassword()");
|
|
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
|
|
LOGGER.debug("== USER ? {}", gc.userByToken);
|
|
|
|
if (data == null) {
|
|
throw new InputException("data", "No data set...");
|
|
}
|
|
if (data.newPassword == null || data.newPassword.length() != 128) {
|
|
throw new InputException("newPassword", "null password, or wrong hash size");
|
|
}
|
|
final UserAuth user = checkAuthUser(data.method, data.login, data.time, data.password);
|
|
if (user == null) {
|
|
throw new SystemException("Fail to retrieve the user... (impossible case)");
|
|
}
|
|
// Process the update:
|
|
user.password = data.newPassword;
|
|
DataAccess.update(user, user.id, List.of("password"));
|
|
}
|
|
|
|
@GET
|
|
@Path("is_login_exist")
|
|
@PermitAll
|
|
public Boolean isLoginExist(@QueryParam("login") final String login) throws Exception {
|
|
LOGGER.debug("checkLogin: '{}'", login);
|
|
final List<UserAuth> out = DataAccess.getsWhere(UserAuth.class,
|
|
new Condition(new QueryCondition("login", "=", login)));
|
|
return out.size() >= 1;
|
|
}
|
|
|
|
// TODO: add an application TOKEN and permit only 50 requested (maybe add an option to disable it).
|
|
@GET
|
|
@Path("is_email_exist")
|
|
@PermitAll
|
|
public Boolean isEmailExist(@QueryParam("email") final String email) throws Exception {
|
|
LOGGER.debug("checkEmail: {}", email);
|
|
final List<UserAuth> out = DataAccess.getsWhere(UserAuth.class,
|
|
new Condition(new QueryCondition("email", "=", email)));
|
|
return out.size() >= 1;
|
|
}
|
|
|
|
private UserAuth checkAuthUser(final String method, final String login, final String time, final String password)
|
|
throws Exception {
|
|
// check good version:
|
|
if (!"v1".contentEquals(method)) {
|
|
throw new InputException("method", "Authentiocate-method-error (wrong version: '" + method + "')");
|
|
}
|
|
// verify login or email is correct:
|
|
if (login.length() < 6) {
|
|
throw new InputException("login", "Authentiocate-method-error (email or login too small: '" + login + "')");
|
|
}
|
|
if (password == null || password.length() != 128) {
|
|
throw new InputException("password", "null password, or wrong hash size");
|
|
}
|
|
// email or login?
|
|
String query = "login";
|
|
if (login.contains("@")) {
|
|
query = "email";
|
|
}
|
|
final UserAuth user = DataAccess.getWhere(UserAuth.class, new Condition(new QueryCondition(query, "=", login)));
|
|
|
|
if (user == null) {
|
|
throw new FailException(Response.Status.PRECONDITION_FAILED,
|
|
"FAIL Authentiocate-wrong email/login '" + login + "')");
|
|
}
|
|
// Check the password:
|
|
final String passwodCheck = getSHA512("login='" + login + "';pass='" + user.password + "';date='" + time + "'");
|
|
if (!passwodCheck.contentEquals(password)) {
|
|
throw new FailException(Response.Status.PRECONDITION_FAILED, "Password error ...");
|
|
}
|
|
LOGGER.debug(" ==> pass nearly all test : admin={} blocked={} removed={}", user.admin, user.blocked,
|
|
user.removed);
|
|
if (user.blocked || user.removed) {
|
|
throw new FailException(Response.Status.UNAUTHORIZED, "FAIL Authentiocate");
|
|
}
|
|
return user;
|
|
}
|
|
|
|
@POST
|
|
@Path("get_token")
|
|
@PermitAll
|
|
@Consumes(MediaType.APPLICATION_JSON)
|
|
public GetToken getToken(final DataGetToken data) throws Exception {
|
|
LOGGER.info("User Authenticate: {}", data.login());
|
|
final UserAuth user = checkAuthUser(data.method(), data.login(), data.time(), data.password());
|
|
// at the point the user has been not deleted and not blocked.
|
|
// this authentication is valid only for Karso ==> not for the application
|
|
final int expirationTimeInMinutes = ConfigVariable.getAuthExpirationTime();
|
|
|
|
// Get the USER Right (Note: by construction KARSO have application ID = KARSO_INITIALISATION_ID
|
|
final Map<String, Object> ssoRight = RightResource.getUserRight(user.id,
|
|
Initialization.KARSO_INITIALISATION_ID);
|
|
if (!ssoRight.containsKey("USER")) {
|
|
// If the USER is not override, the system add by default USER
|
|
ssoRight.put("USER", true);
|
|
}
|
|
LOGGER.debug("Get new token with right: {}", ssoRight);
|
|
final Map<String, Object> outRight = new HashMap<>();
|
|
final String applicationName = "karso";
|
|
// we set the right in the under map to manage multiple application group right. and in some application user can see other user or all user of the application
|
|
outRight.put(applicationName, ssoRight);
|
|
// TODO: maybe correct this get of TTL...
|
|
final String ret = JWTWrapper.generateJWToken(user.id, user.login, "KarAuth", applicationName, outRight,
|
|
expirationTimeInMinutes);
|
|
if (ret == null) {
|
|
throw new SystemException("Missing internal JWT system ==> can not sign anything ...");
|
|
}
|
|
// Update last connection:
|
|
final UserAuth newUser = new UserAuth();
|
|
newUser.lastConnection = Timestamp.valueOf(LocalDateTime.now());
|
|
DataAccess.update(newUser, user.id, List.of("lastConnection"));
|
|
|
|
//LOGGER.debug(" ==> generate token: {}", ret);
|
|
return new GetToken(ret);
|
|
}
|
|
|
|
public static String bytesToHex(final byte[] bytes) {
|
|
final StringBuilder sb = new StringBuilder();
|
|
for (final byte b : bytes) {
|
|
sb.append(String.format("%02x", b));
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
public String getSHA512(final String passwordToHash) {
|
|
try {
|
|
final MessageDigest md = MessageDigest.getInstance("SHA-512");
|
|
final byte[] bytes = md.digest(passwordToHash.getBytes(StandardCharsets.UTF_8));
|
|
return bytesToHex(bytes);
|
|
} catch (final NoSuchAlgorithmException e) {
|
|
e.printStackTrace();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
}
|