503 lines
19 KiB
Java
503 lines
19 KiB
Java
package org.gcube.service.idm.rest;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import org.gcube.common.security.Owner;
|
|
import org.gcube.common.security.providers.SecretManagerProvider;
|
|
import org.gcube.common.security.secrets.Secret;
|
|
import org.gcube.service.idm.IdMManager;
|
|
import org.gcube.service.idm.controller.AuthController;
|
|
import org.gcube.service.idm.controller.JWTController;
|
|
import org.gcube.service.idm.controller.KCUserController;
|
|
import org.gcube.service.idm.controller.LiferayProfileClient;
|
|
import org.gcube.service.idm.keycloack.KkClientFactory;
|
|
import org.gcube.service.idm.serializers.IdmObjectSerializator;
|
|
import org.gcube.service.rest.ErrorMessages;
|
|
import org.gcube.service.rest.ResponseBean;
|
|
import org.gcube.service.rest.ResponseBeanMap;
|
|
import org.gcube.service.rest.ResponseBeanPaginated;
|
|
import org.gcube.smartgears.annotations.ManagedBy;
|
|
import org.gcube.vomanagement.usermanagement.model.GCubeUser;
|
|
import org.keycloak.admin.client.resource.RealmResource;
|
|
import org.keycloak.admin.client.resource.UserResource;
|
|
import org.keycloak.representations.idm.GroupRepresentation;
|
|
import org.keycloak.representations.idm.MappingsRepresentation;
|
|
import org.keycloak.representations.idm.UserRepresentation;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.webcohesion.enunciate.metadata.rs.RequestHeader;
|
|
import com.webcohesion.enunciate.metadata.rs.RequestHeaders;
|
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|
|
|
import jakarta.ws.rs.BadRequestException;
|
|
import jakarta.ws.rs.DefaultValue;
|
|
import jakarta.ws.rs.ForbiddenException;
|
|
import jakarta.ws.rs.GET;
|
|
import jakarta.ws.rs.InternalServerErrorException;
|
|
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.MediaType;
|
|
import jakarta.ws.rs.core.Response;
|
|
|
|
/**
|
|
* <p>
|
|
* The REST API to interact with the keycloak users
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* "Member" users can only invoke metods on his own user (/me)
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* Users with "idm-service-read" or "idm-service-admin" role can invoke metods
|
|
* on all the users
|
|
* </p>
|
|
*
|
|
* @author Alfredo Oliviero (ISTI - CNR)
|
|
*/
|
|
|
|
@ManagedBy(IdMManager.class)
|
|
@RequestHeaders({
|
|
@RequestHeader(name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources"),
|
|
@RequestHeader(name = "Content-Type", description = "application/json")
|
|
})
|
|
@Path("users")
|
|
public class UserAPI {
|
|
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(UserAPI.class);
|
|
|
|
/**
|
|
* Returns infos about the authenticated user
|
|
*
|
|
* <ul>
|
|
* <li>owner: the authenticated user</li>
|
|
* <li>profile: the profile of the user in the Liferay CMS (only if the user is
|
|
* not a service)</li>
|
|
* <li>user: the user representation from the authentication service</li>
|
|
* </uL>
|
|
*
|
|
* if the optional parameter inspect is passed as true, returns additional
|
|
* values:
|
|
* <ul>
|
|
* <li>verify: the result of introspection of the auth token on the
|
|
* authentication service</li>
|
|
* <li>roles: the authenticated user</li>
|
|
* <li>groups: the authenticated user</li>
|
|
* <li>groupRolesRealm: ...</li>
|
|
* <li>groupRolesClients: ...</li>
|
|
* </ul>
|
|
*
|
|
* @param inspect adds additional inspection values to the result
|
|
* @returns infos about the authenticated user
|
|
*
|
|
*/
|
|
@GET
|
|
@Path("/me")
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
@StatusCodes({
|
|
@ResponseCode(code = 200, condition = "current user informations"),
|
|
@ResponseCode(code = 403, condition = ErrorMessages.NOT_USER_TOKEN_CONTEXT_USED),
|
|
@ResponseCode(code = 404, condition = ErrorMessages.INVALID_ATTRIBUTE),
|
|
@ResponseCode(code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
|
|
})
|
|
public Response getMe(
|
|
@QueryParam("inspect") @DefaultValue("false") Boolean inspect) {
|
|
logger.info("/users/me");
|
|
ResponseBean responseBean = new ResponseBean();
|
|
ObjectMapper objectMapper = IdmObjectSerializator.getSerializer();
|
|
|
|
Secret secret = SecretManagerProvider.get();
|
|
Owner owner = secret.getOwner();
|
|
String username = owner.getId();
|
|
|
|
// if (owner.isApplication()) {
|
|
// // only users can use "me"
|
|
// throw new ForbiddenException(ErrorMessages.NOT_USER_TOKEN_CONTEXT_USED);
|
|
// }
|
|
|
|
Map<String, Object> result = getUserData(username, !owner.isApplication(), inspect);
|
|
responseBean.setResult(result);
|
|
result.put("owner", owner);
|
|
|
|
try {
|
|
if (inspect) {
|
|
String token = AuthController.getAccessToken();
|
|
result.put("verify", JWTController.decodeJwtToken(token));
|
|
}
|
|
responseBean.setSuccess(true);
|
|
String jsonData = objectMapper.writeValueAsString(responseBean);
|
|
return Response.ok(jsonData).build();
|
|
} catch (JsonProcessingException e) {
|
|
e.printStackTrace();
|
|
throw new InternalServerErrorException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns informations about the user received as parameter
|
|
*
|
|
* Only users with "idm-service-read" or "idm-service-admin" role can invoke
|
|
* this method
|
|
*
|
|
* <ul>
|
|
* <li>profile: the profile of the user in the Liferay CMS (only if the user is
|
|
* not a service)</li>
|
|
* <li>user: the user representation from the authentication service</li>
|
|
* </uL>
|
|
*
|
|
* if the optional parameter inspect is passed as true, returns additional
|
|
* values:
|
|
* <ul>
|
|
* <li>roles: the authenticated user</li>
|
|
* <li>groups: the authenticated user</li>
|
|
* <li>groupRolesRealm: ...</li>
|
|
* <li>groupRolesClients: ...</li>
|
|
* </ul>
|
|
*
|
|
* @param username the username of the user
|
|
* @param inspect adds additional inspection values to the result
|
|
* @returns infos about the user
|
|
*/
|
|
|
|
@GET
|
|
@Path("/{username}")
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
@StatusCodes({
|
|
@ResponseCode(code = 200, condition = "user informations"),
|
|
@ResponseCode(code = 403, condition = ErrorMessages.NOT_USER_TOKEN_CONTEXT_USED),
|
|
@ResponseCode(code = 404, condition = ErrorMessages.INVALID_ATTRIBUTE),
|
|
@ResponseCode(code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
|
|
})
|
|
public Response getUser(
|
|
@PathParam("username") String username,
|
|
@QueryParam("inspect") @DefaultValue("false") Boolean inspect) {
|
|
|
|
ResponseBeanMap responseBean = new ResponseBeanMap();
|
|
ObjectMapper objectMapper = IdmObjectSerializator.getSerializer();
|
|
|
|
if (!AuthController.checkAnyRole(AuthController.ACCESS_READ_ROLES)) {
|
|
// the user can see only his profile
|
|
throw new ForbiddenException(ErrorMessages.USER_NOT_AUTHORIZED_PRIVATE);
|
|
}
|
|
|
|
Secret secret = SecretManagerProvider.get();
|
|
Owner owner = secret.getOwner();
|
|
|
|
try {
|
|
Map<String, Object> result = getUserData(username, !owner.isApplication(), inspect);
|
|
responseBean.setResult(result);
|
|
responseBean.setSuccess(true);
|
|
|
|
String jsonData = objectMapper.writeValueAsString(responseBean);
|
|
return Response.ok(jsonData).build();
|
|
} catch (JsonProcessingException e) {
|
|
e.printStackTrace();
|
|
throw new InternalServerErrorException(e);
|
|
}
|
|
}
|
|
|
|
protected Map<String, Object> getUserData(String username, Boolean getProfile, Boolean isInspect) {
|
|
Map<String, Object> result = new HashMap<>();
|
|
|
|
UserRepresentation user = KCUserController.getUserByUsername(username);
|
|
result.put("user", user);
|
|
|
|
if (getProfile) {
|
|
GCubeUser profile = LiferayProfileClient.getUserProfileByUsername(username);
|
|
result.put("profile", profile);
|
|
// throw new ForbiddenException(ErrorMessages.NOT_USER_TOKEN_CONTEXT_USED);
|
|
}
|
|
|
|
if (isInspect) {
|
|
UserResource userResource = KCUserController.getUserResourceByUsername(username);
|
|
MappingsRepresentation roles = userResource.roles().getAll();
|
|
result.put("roles", roles);
|
|
|
|
List<GroupRepresentation> groups = userResource.groups();
|
|
result.put("groups", groups);
|
|
|
|
HashMap<String, Object> groupRolesRealm = new HashMap<String, Object>();
|
|
HashMap<String, Object> groupRolesClients = new HashMap<String, Object>();
|
|
|
|
result.put("groupRolesRealm", groupRolesRealm);
|
|
result.put("groupRolesClients", groupRolesClients);
|
|
|
|
for (GroupRepresentation g : groups) {
|
|
groupRolesClients.put(g.getId(), g.getClientRoles());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* @returns the owner object of the authenticated user
|
|
*/
|
|
@GET
|
|
@Path("/me/owner")
|
|
@Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" })
|
|
@StatusCodes({
|
|
@ResponseCode(code = 200, condition = "infos about the owner of the auth token"),
|
|
@ResponseCode(code = 403, condition = ErrorMessages.NOT_USER_TOKEN_CONTEXT_USED),
|
|
@ResponseCode(code = 404, condition = ErrorMessages.INVALID_ATTRIBUTE),
|
|
@ResponseCode(code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
|
|
})
|
|
public Response getCurrentUser() {
|
|
ResponseBean responseBean = new ResponseBean();
|
|
|
|
Secret secret = SecretManagerProvider.get();
|
|
Owner owner = secret.getOwner();
|
|
|
|
try {
|
|
|
|
// UserResource user = KCUserController.getUserById();
|
|
responseBean.setResult(owner);
|
|
responseBean.setSuccess(true);
|
|
|
|
ObjectMapper objectMapper = IdmObjectSerializator.getSerializer();
|
|
|
|
String jsonData = objectMapper.writeValueAsString(responseBean);
|
|
return Response.ok(jsonData).build();
|
|
|
|
} catch (JsonProcessingException e) {
|
|
e.printStackTrace();
|
|
throw new InternalServerErrorException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns the result of introspection of the auth token on the
|
|
* authentication service
|
|
*/
|
|
@GET
|
|
@Path("/me/verify")
|
|
@Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" })
|
|
@StatusCodes({
|
|
@ResponseCode(code = 200, condition = "decode the token"),
|
|
@ResponseCode(code = 403, condition = ErrorMessages.NOT_USER_TOKEN_CONTEXT_USED),
|
|
@ResponseCode(code = 404, condition = ErrorMessages.INVALID_ATTRIBUTE),
|
|
@ResponseCode(code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
|
|
})
|
|
public Response getInrospectioCurrenttUser() {
|
|
ResponseBean responseBean = new ResponseBean();
|
|
|
|
try {
|
|
|
|
String token = AuthController.getAccessToken();
|
|
responseBean.setResult(JWTController.decodeJwtToken(token));
|
|
|
|
// UserResource user = KCUserController.getUserById();
|
|
responseBean.setSuccess(true);
|
|
|
|
ObjectMapper objectMapper = IdmObjectSerializator.getSerializer();
|
|
|
|
String jsonData = objectMapper.writeValueAsString(responseBean);
|
|
return Response.ok(jsonData).build();
|
|
|
|
} catch (JsonProcessingException e) {
|
|
e.printStackTrace();
|
|
throw new InternalServerErrorException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns informations about the user received as parameter (can be also me)
|
|
*
|
|
* Normal member users can use only "me" or his username as parameter
|
|
*
|
|
* Users with "idm-service-read" or "idm-service-admin" role can invoke
|
|
* this method with any username
|
|
*
|
|
* accepted parameters are:
|
|
*
|
|
* <ul>
|
|
* <li>profile: returns the profile of the user in the Liferay CMS (only if the
|
|
* user is
|
|
* not a service)</li>
|
|
* <li>email: returns the email of the user from the authentication service</li>
|
|
*
|
|
* <li>user: the user representation from the authentication service</li>
|
|
* </uL>
|
|
*
|
|
* if the optional parameter inspect is passed as true, returns additional
|
|
* values:
|
|
* <ul>
|
|
* <li>roles_realm: roles in realm for the user from the authentication
|
|
* service</li>
|
|
* <li>roles_clients: roles in clients for the user from the authentication
|
|
* service</li>
|
|
* <li>groups: id of the user from the authentication service</li>
|
|
* <li>username: username of the user from the authentication service</li>
|
|
* <li>name: Fullname of the user from the authentication service</li>
|
|
* <li>attributes: attributes of the user from the authentication service</li>
|
|
* <li>user: full user from the authentication service</li>
|
|
* <li>profile: profile of the user from the Liferay CMS service</li>
|
|
* </ul>
|
|
*
|
|
* @param username the username of the user
|
|
* @param parameter the parameter to obtain. accepts profile, email,
|
|
* roles_realm, roles_clients, groups, id, username , name,
|
|
* attributes, user
|
|
* @param inspect adds additional inspection values to the result
|
|
* @returns infos about the user
|
|
*/
|
|
|
|
public enum USER_DETAILS {
|
|
profile, email, roles_realm, roles_clients,
|
|
groups, id, username, name, attributes, user
|
|
}
|
|
|
|
@GET
|
|
@Path("/{username}/{parameter}")
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
@StatusCodes({
|
|
@ResponseCode(code = 200, condition = "decode the token"),
|
|
@ResponseCode(code = 403, condition = ErrorMessages.NOT_USER_TOKEN_CONTEXT_USED),
|
|
@ResponseCode(code = 404, condition = ErrorMessages.INVALID_ATTRIBUTE),
|
|
@ResponseCode(code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
|
|
})
|
|
public Response getUserParameter(
|
|
@PathParam("username") String username,
|
|
@PathParam("parameter") USER_DETAILS parameter) {
|
|
|
|
ResponseBean responseBean = new ResponseBean();
|
|
ObjectMapper objectMapper = IdmObjectSerializator.getSerializer();
|
|
|
|
Secret secret = SecretManagerProvider.get();
|
|
Owner owner = secret.getOwner();
|
|
|
|
if (username.equals("me")) {
|
|
|
|
username = owner.getId();
|
|
}
|
|
|
|
if (!AuthController.checkAnyRole(AuthController.ACCESS_READ_ROLES)
|
|
&& !AuthController.userIsMe(username, owner)) {
|
|
// the user can see only his profile
|
|
throw new ForbiddenException(ErrorMessages.USER_NOT_AUTHORIZED_PRIVATE);
|
|
}
|
|
|
|
try {
|
|
if (parameter.equals(USER_DETAILS.profile)) {
|
|
|
|
GCubeUser profile = LiferayProfileClient.getUserProfileByUsername(username);
|
|
responseBean.setResult(profile);
|
|
|
|
String jsonData = objectMapper.writeValueAsString(responseBean);
|
|
return Response.ok(jsonData).build();
|
|
}
|
|
|
|
UserRepresentation user = KCUserController.getUserByUsername(username);
|
|
|
|
if (parameter.equals(USER_DETAILS.email))
|
|
responseBean.setResult(user.getEmail());
|
|
|
|
else if (parameter.equals(USER_DETAILS.roles_realm))
|
|
responseBean.setResult(user.getRealmRoles());
|
|
|
|
else if (parameter.equals(USER_DETAILS.roles_clients))
|
|
responseBean.setResult(user.getClientRoles());
|
|
|
|
else if (parameter.equals(USER_DETAILS.groups))
|
|
responseBean.setResult(user.getGroups());
|
|
|
|
else if (parameter.equals(USER_DETAILS.id))
|
|
responseBean.setResult(user.getId());
|
|
|
|
else if (parameter.equals(USER_DETAILS.username))
|
|
responseBean.setResult(user.getUsername());
|
|
|
|
else if (parameter.equals(USER_DETAILS.name))
|
|
responseBean.setResult(user.getFirstName() + " " + user.getLastName());
|
|
|
|
else if (parameter.equals(USER_DETAILS.attributes))
|
|
responseBean.setResult(user.getAttributes());
|
|
|
|
else if (parameter.equals(USER_DETAILS.user) || parameter == null)
|
|
responseBean.setResult(user);
|
|
|
|
else
|
|
throw new BadRequestException("unknow parameter " + parameter);
|
|
|
|
responseBean.setSuccess(true);
|
|
|
|
String jsonData = objectMapper.writeValueAsString(responseBean);
|
|
return Response.ok(jsonData).build();
|
|
} catch (JsonProcessingException e) {
|
|
e.printStackTrace();
|
|
throw new InternalServerErrorException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search users in all realm, filtered according to query parameters.
|
|
*
|
|
* @param format response format
|
|
* @param exact Boolean which defines whether the params 'last', 'first',
|
|
* 'email' and 'username' must match exactly. default true
|
|
* @param username A String contained in username, or the complete username,
|
|
* if
|
|
* param 'exact' is true
|
|
* @param firstName A String contained in firstName, or the complete
|
|
* firstName,
|
|
* if param 'exact' is true
|
|
* @param lastName A String contained in firstName, or the complete
|
|
* firstName,
|
|
* if param 'exact' is true
|
|
* @param firstResult pagination offset
|
|
* @param maxResults maximum results size
|
|
* @param enabled Boolean representing if user is enabled or not
|
|
* @param email A String contained in email, or the complete email, if
|
|
* param 'exact' is true
|
|
*/
|
|
@GET
|
|
@Path("/search")
|
|
@Produces(MediaType.APPLICATION_JSON)
|
|
public Response search(
|
|
@QueryParam("format") @DefaultValue("username") KCUserController.REPR format,
|
|
@QueryParam("exact") @DefaultValue("true") Boolean exact,
|
|
|
|
@QueryParam("username") String username,
|
|
@QueryParam("firsnName") String firstName,
|
|
@QueryParam("lastName") String lastName,
|
|
@QueryParam("email") String email,
|
|
@QueryParam("first") @DefaultValue("0") int firstResult,
|
|
@QueryParam("max") @DefaultValue("100") int maxResults,
|
|
@QueryParam("enabled") @DefaultValue("true") Boolean enabled) {
|
|
|
|
ResponseBean responseBean;
|
|
responseBean = new ResponseBeanPaginated(firstResult, maxResults);
|
|
|
|
try {
|
|
if (!format.equals(KCUserController.REPR.username)
|
|
&& !AuthController.checkAnyRole(AuthController.ACCESS_READ_ROLES)) {
|
|
// the user can see only his profile
|
|
throw new ForbiddenException(ErrorMessages.USER_NOT_AUTHORIZED_PRIVATE);
|
|
}
|
|
|
|
RealmResource realm = KkClientFactory.getSingleton().getKKRealm();
|
|
Boolean briefRepresentation = !KCUserController.REPR.full.equals(format);
|
|
List<UserRepresentation> users = realm.users().search(
|
|
username, firstName, lastName, email,
|
|
firstResult, maxResults,
|
|
enabled, briefRepresentation, exact);
|
|
|
|
responseBean.setResult(KCUserController.formatList(users, format));
|
|
responseBean.setSuccess(true);
|
|
|
|
ObjectMapper objectMapper = IdmObjectSerializator.getSerializer();
|
|
String jsonData = objectMapper.writeValueAsString(responseBean);
|
|
return Response.ok(jsonData).build();
|
|
|
|
} catch (JsonProcessingException e) {
|
|
e.printStackTrace();
|
|
throw new InternalServerErrorException(e);
|
|
}
|
|
}
|
|
}
|