social-networking-library-ws/src/main/java/org/gcube/portal/social/networking/ws/methods/v2/Users.java

596 lines
21 KiB
Java

package org.gcube.portal.social.networking.ws.methods.v2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.validation.ValidationException;
import javax.validation.constraints.NotNull;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.gcube.common.authorization.library.provider.AuthorizationProvider;
import org.gcube.common.authorization.library.utils.Caller;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.common.scope.impl.ScopeBean;
import org.gcube.common.scope.impl.ScopeBean.Type;
import org.gcube.portal.social.networking.caches.UsersCache;
import org.gcube.portal.social.networking.liferay.ws.GroupManagerWSBuilder;
import org.gcube.portal.social.networking.liferay.ws.KeycloakAPICredentials;
import org.gcube.portal.social.networking.liferay.ws.RoleManagerWSBuilder;
import org.gcube.portal.social.networking.liferay.ws.UserManagerWSBuilder;
import org.gcube.portal.social.networking.ws.outputs.ResponseBean;
import org.gcube.portal.social.networking.ws.utils.ErrorMessages;
import org.gcube.portal.social.networking.ws.utils.TokensUtils;
import org.gcube.portal.social.networking.ws.utils.UserProfileExtendedWithVerifiedEmail;
import org.gcube.vomanagement.usermanagement.GroupManager;
import org.gcube.vomanagement.usermanagement.RoleManager;
import org.gcube.vomanagement.usermanagement.UserManager;
import org.gcube.vomanagement.usermanagement.model.GCubeRole;
import org.gcube.vomanagement.usermanagement.model.GCubeUser;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.KeycloakBuilder;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.slf4j.LoggerFactory;
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;
/**
* REST interface for the social networking library (users).
*/
@Path("2/users")
@RequestHeaders ({
@RequestHeader( name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources"),
@RequestHeader( name = "Content-Type", description = "application/json")
})
public class Users {
// Logger
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Users.class);
private static final String NOT_USER_TOKEN_CONTEXT_USED = "User's information can only be retrieved through a user token (not qualified)";
private static final List<String> GLOBAL_ROLES_ALLOWED_BY_LOCAL_CALL_METHOD = Arrays.asList("DataMiner-Manager","Quota-Manager");
/**
* Read a user's custom attribute. The user is the one owning the token
* @param attributeKey The key of the attribute to be read
* @return the user's custom attribute
* @throws ValidationException
*/
@GET
@Path("get-custom-attribute/")
@Produces(MediaType.APPLICATION_JSON)
@StatusCodes ({
@ResponseCode ( code = 200, condition = "Successful read of the attribute, reported in the 'result' field of the returned object"),
@ResponseCode ( code = 404, condition = "Such an attribute doesn't exist"),
@ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
})
public Response readCustomAttr(
@QueryParam("attribute")
@NotNull(message="attribute name is missing")
String attributeKey
) throws ValidationException {
Caller caller = AuthorizationProvider.instance.get();
String username = caller.getClient().getId();
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
if(!TokensUtils.isUserTokenDefault(caller)){
status = Status.FORBIDDEN;
responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED);
logger.warn("Trying to access users method via a token different than USER is not allowed");
}else{
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
try{
GCubeUser user = userManager.getUserByUsername(username);
String toReturn = (String)userManager.readCustomAttr(user.getUserId(), attributeKey);
responseBean.setSuccess(true);
responseBean.setResult(toReturn);
}catch(Exception e){
logger.error("Unable to retrieve attribute for user.", e);
responseBean.setMessage(e.toString());
responseBean.setSuccess(false);
status = Status.NOT_FOUND;
}
}
return Response.status(status).entity(responseBean).build();
}
/**
* Read the user's fullname. The user is the one owning the token
* @return the user's fullname
*/
@GET
@Path("get-fullname")
@Produces(MediaType.APPLICATION_JSON)
@StatusCodes ({
@ResponseCode ( code = 200, condition = "The user's fullname is reported in the 'result' field of the returned object"),
@ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
})
public Response getUserFullname(){
Caller caller = AuthorizationProvider.instance.get();
String username = caller.getClient().getId();
String fullName = null;
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
if(!TokensUtils.isUserTokenDefault(caller)){
status = Status.FORBIDDEN;
responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED);
logger.warn("Trying to access users method via a token different than USER is not allowed");
}else{
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
try{
GCubeUser user = userManager.getUserByUsername(username);
fullName = user.getFullname();
logger.info("Found fullname " + fullName + " for user " + username);
responseBean.setResult(fullName);
responseBean.setSuccess(true);
}catch(Exception e){
logger.error("Unable to retrieve attribute for user.", e);
status = Status.INTERNAL_SERVER_ERROR;
}
}
return Response.status(status).entity(responseBean).build();
}
/**
* Read the user's email address. The user is the one owning the token
* @return rhe user's email address
*/
@GET
@Path("get-email")
@Produces(MediaType.APPLICATION_JSON)
@StatusCodes ({
@ResponseCode ( code = 200, condition = "The user's email is reported in the 'result' field of the returned object"),
@ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
})
public Response getUserEmail(){
Caller caller = AuthorizationProvider.instance.get();
String username = caller.getClient().getId();
String email = null;
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
if(!TokensUtils.isUserTokenDefault(caller)){
status = Status.FORBIDDEN;
responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED);
logger.warn("Trying to access users method via a token different than USER is not allowed");
}else{
try{
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
GCubeUser user = userManager.getUserByUsername(username);
email = user.getEmail();
logger.info("Found email " + email + " for user " + username);
responseBean.setResult(email);
responseBean.setSuccess(true);
}catch(Exception e){
logger.error("Unable to retrieve attribute for user.", e);
status = Status.INTERNAL_SERVER_ERROR;
}
}
return Response.status(status).entity(responseBean).build();
}
/**
* Get the profile associated to the token
* @responseExample application/json { "success" : true, "message" : null, "result" : { "user_id" : 23769487, "username" : "john.smith", "email" : "********", "first_name" : "John", "middle_name" : "", "last_name" : "Smith", "fullname" : "John Smith", "registration_date" : 1475151491415, "user_avatar_url" : "https://******D", "male" : true, "job_title" : "", "location_industry" : "no", "custom_attrs_map" : null, "email_addresses" : [ ], "screen_name" : "john.smith", "user_avatar_id" : "https://****sY%3D" } }
* @return the user's profile. The user is the one owning the token
*/
@GET
@Path("get-profile")
@Produces(MediaType.APPLICATION_JSON)
@StatusCodes ({
@ResponseCode ( code = 200, condition = "The user's profile is reported in the 'result' field of the returned object"),
@ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
})
public Response getUserProfile(){
Caller caller = AuthorizationProvider.instance.get();
String username = caller.getClient().getId();
GCubeUser user = null;
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
if(!TokensUtils.isUserToken(caller)){
status = Status.FORBIDDEN;
responseBean.setMessage("User's information can only be retrieved through a user Jtoken");
logger.warn("Trying to access users method via a token different than 'user-token' is not allowed");
}else{
try{
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
user = userManager.getUserByUsername(username);
responseBean.setResult(user);
responseBean.setSuccess(true);
}catch(Exception e){
logger.error("Unable to retrieve user's profile", e);
responseBean.setMessage(e.getMessage());
status = Status.INTERNAL_SERVER_ERROR;
}
}
return Response.status(status).entity(responseBean).build();
}
private static final Function<GCubeUser, UserProfileExtendedWithVerifiedEmail> GCUBE_TO_EXTENDED_PROFILE_MAP_WITH_VERIFIED_EMAIL
= new Function<GCubeUser, UserProfileExtendedWithVerifiedEmail>() {
@Override
public UserProfileExtendedWithVerifiedEmail apply(GCubeUser t) {
if(t == null)
return null;
UserProfileExtendedWithVerifiedEmail profile = new UserProfileExtendedWithVerifiedEmail(t.getUsername(), null, t.getUserAvatarURL(), t.getFullname());
profile.setEmail(t.getEmail());
profile.setFirstName(t.getFirstName());
profile.setJobTitle(t.getJobTitle());
profile.setLastName(t.getLastName());
profile.setLocationIndustry(t.getLocation_industry());
profile.setMale(t.isMale());
profile.setMiddleName(t.getMiddleName());
profile.setVerifiedEmail(true);
return profile;
}
};
/**
* @responseExample application/json {
"id": "john.smith",
"roles": [],
"picture": "https://***gAJ4uVWTA74xwQ6%2FCA72RRinysY%3D",
"name": "John Smith",
"middle_name": "",
"male": true,
"location_industry": "no",
"given_name": "John",
"email": "******",
"job_title": "",
"family_name": "Smith",
"verified_email": true
}
* @return the user's profile compliant with oauth
*/
@GET
@Path("get-oauth-profile")
@Produces(MediaType.APPLICATION_JSON)
public Response getUserOAuthProfile(){
Caller caller = AuthorizationProvider.instance.get();
String username = caller.getClient().getId();
String scope = ScopeProvider.instance.get();
GCubeUser user = null;
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
UserProfileExtendedWithVerifiedEmail userWithEmailVerified = null;
if(!TokensUtils.isUserToken(caller)){
status = Status.FORBIDDEN;
responseBean.setMessage(NOT_USER_TOKEN_CONTEXT_USED);
logger.warn("Trying to access users method via a token different than USER is not allowed");
}else{
try{
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager();
GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager();
user = userManager.getUserByUsername(username);
userWithEmailVerified = GCUBE_TO_EXTENDED_PROFILE_MAP_WITH_VERIFIED_EMAIL.apply(user);
List<GCubeRole> roles = roleManager.listRolesByUserAndGroup(user.getUserId(), groupManager.getGroupIdFromInfrastructureScope(scope));
List<String> rolesNames = new ArrayList<String>();
for (GCubeRole gCubeRole : roles) {
rolesNames.add(gCubeRole.getRoleName());
}
userWithEmailVerified.setRoles(rolesNames);
//responseBean.setResult(userWithEmailVerified);
responseBean.setSuccess(true);
}catch(Exception e){
logger.error("Unable to retrieve user's profile", e);
responseBean.setMessage(e.getMessage());
status = Status.INTERNAL_SERVER_ERROR;
}
}
logger.debug("returning: "+userWithEmailVerified.toString());
return Response.status(status).entity(userWithEmailVerified).build();
}
/**
* @responseExample application/json {
"success": true,
"message": null,
"result": [
"john.smith",
"marco.polo"
]
}
* Get the list of usernames belonging to a given context
* @return the list of usernames for the user belonging to the context linked to the provided auth token
*/
@GET
@Path("get-all-usernames")
@Produces(MediaType.APPLICATION_JSON)
@StatusCodes ({
@ResponseCode ( code = 200, condition = "The list of usernames is put into the 'result' field of the returned object"),
@ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
})
public Response getAllUserNames(){
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
List<String> usernames = new ArrayList<String>();
try{
GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager();
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
long groupId = groupManager.getGroupIdFromInfrastructureScope(ScopeProvider.instance.get());
// first retrieve ids
List<Long> userIds = userManager.getUserIdsByGroup(groupId);
// check info in cache when available
UsersCache cache = UsersCache.getSingleton();
for (Long userId : userIds) {
if(cache.getUser(userId) == null){
GCubeUser user = userManager.getUserById(userId);
if(user != null){
usernames.add(user.getUsername());
cache.pushEntry(userId, user);
}
}else
usernames.add(cache.getUser(userId).getUsername());
}
responseBean.setResult(usernames);
responseBean.setSuccess(true);
}catch(Exception e){
logger.error("Unable to retrieve user's usernames", e);
responseBean.setMessage(e.getMessage());
status = Status.INTERNAL_SERVER_ERROR;
}
return Response.status(status).entity(responseBean).build();
}
/**
* Get the map of couples username/fullname of the users belonging to a given context
* @return the map of couples username/fullname of the users belonging to the context linked to the provided token.
*/
@GET
@Path("get-all-fullnames-and-usernames")
@StatusCodes ({
@ResponseCode ( code = 200, condition = "The map is put into the 'result' field of the returned object"),
@ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
})
@Produces(MediaType.APPLICATION_JSON)
public Response getFullnamesAndUsernames(){
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
Map<String, String> usernamesAndFullnames = new HashMap<String, String>();
try{
GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager();
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
long groupId = groupManager.getGroupIdFromInfrastructureScope(ScopeProvider.instance.get());
// first retrieve ids
List<Long> userIds = userManager.getUserIdsByGroup(groupId);
// check info in cache when available
UsersCache cache = UsersCache.getSingleton();
for (Long userId : userIds) {
if(cache.getUser(userId) == null){
GCubeUser user = userManager.getUserById(userId);
if(user != null){
usernamesAndFullnames.put(user.getUsername(), user.getFullname());
cache.pushEntry(userId, user);
}
}else
usernamesAndFullnames.put(cache.getUser(userId).getUsername(), cache.getUser(userId).getFullname());
}
responseBean.setResult(usernamesAndFullnames);
responseBean.setSuccess(true);
}catch(Exception e){
logger.error("Unable to retrieve user's usernames", e);
responseBean.setMessage(e.getMessage());
status = Status.INTERNAL_SERVER_ERROR;
}
return Response.status(status).entity(responseBean).build();
}
/**
* Get the list of users having a given global-role, e.g. 'Administrator'. (Legacy)
* @param roleName the name of the role to be checked (e.g. Administrator)
* @return the list of users having a given global-role
*/
@GET
@Path("get-usernames-by-global-role")
@Produces(MediaType.APPLICATION_JSON)
@StatusCodes ({
@ResponseCode ( code = 200, condition = "The list is put into the 'result' field of the returned object"),
@ResponseCode ( code = 500, condition = ErrorMessages.ERROR_IN_API_RESULT)
})
public Response getUsernamesByGlobalRole(
@QueryParam("role-name") String roleName){
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
// this method can only be called from IS scope (except for roles in GLOBAL_ROLES_ALLOWED_BY_LOCAL)
ScopeBean scopeInfo = new ScopeBean(ScopeProvider.instance.get());
if(!scopeInfo.is(Type.INFRASTRUCTURE)){
status = Status.BAD_REQUEST;
responseBean.setMessage("This method can only be called with an infrastructure token");
}else{
List<String> usernames = new ArrayList<String>();
try{
RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager();
long globalRoleId = roleManager.getRoleIdByName(roleName);
if(globalRoleId > 0){
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
List<GCubeUser> users = userManager.listUsersByGlobalRole(globalRoleId);
if(users != null){
for (GCubeUser gCubeUser : users) {
usernames.add(gCubeUser.getUsername());
}
}
responseBean.setResult(usernames);
responseBean.setSuccess(true);
}else{
responseBean.setMessage("No global role exists whit such a name");
status = Status.BAD_REQUEST;
}
}catch(Exception e){
logger.error("Unable to retrieve user's usernames", e);
responseBean.setMessage(e.getMessage());
status = Status.INTERNAL_SERVER_ERROR;
}
}
return Response.status(status).entity(responseBean).build();
}
/**
* @pathExample /get-usernames-by-role?role-name=VRE-Manager
* @param roleName the role name
* @return the usernames having the role in the VRE
* @responseExample application/json {
"success": true,
"message": null,
"result": [
"john.smith",
"marco.polo"
]
}
*/
@GET
@Path("get-usernames-by-role")
@Produces(MediaType.APPLICATION_JSON)
public Response getUsernamesByRole(
@QueryParam("role-name") String roleName){
ResponseBean responseBean = new ResponseBean();
Status status = Status.OK;
String context = ScopeProvider.instance.get();
KeycloakAPICredentials apiService = KeycloakAPICredentials.getSingleton();
Keycloak keycloak;
keycloak = KeycloakBuilder.builder()
.serverUrl(apiService.getServerURL())
.realm(apiService.getRealm())
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.clientId(apiService.getClientid()) //
.clientSecret(apiService.getPassword()).build();
List<String> usernames = new ArrayList<String>();
try {
List<UserRepresentation> users = searchByRole(keycloak, apiService.getRealm(), context, roleName);
if(users != null){
for (UserRepresentation user : users) {
usernames.add(user.getUsername());
}
}
responseBean.setResult(usernames);
responseBean.setSuccess(true);
} catch(Exception e){
logger.error("Unable to retrieve user with the requested role", e);
responseBean.setMessage(e.getMessage());
status = Status.INTERNAL_SERVER_ERROR;
}
return Response.status(status).entity(responseBean).build();
}
private static List<UserRepresentation> searchByRole(Keycloak keycloak, String realmName, String context, String roleName) {
logger.info("Searching by role: {}", roleName);
String clientIdContext = context.replace("/", "%2F") ;
List<ClientRepresentation> clients = keycloak.realm(realmName)
.clients().findByClientId(clientIdContext);
String id = "";
for (ClientRepresentation client : clients) {
logger.info("found client ="+client.getClientId());
logger.info("found client id="+client.getId());
id =client.getId();
}
List<UserRepresentation> users = keycloak.realm(realmName)
.clients()
.get(id).roles().get(roleName)
.getUserMembers(0, 100000);
return users;
}
@GET
@Path("user-exists")
@Produces(MediaType.APPLICATION_JSON)
@Deprecated
public Response existUser(@QueryParam("username") String username){
ResponseBean responseBean = new ResponseBean();
String messageOnError = "This method can be invoked only by using an application token bound to the root context";
Status status = Status.BAD_REQUEST;
responseBean.setMessage(messageOnError);
responseBean.setSuccess(false);
Caller caller = AuthorizationProvider.instance.get();
if(!TokensUtils.isApplicationToken(caller))
return Response.status(status).entity(responseBean).build();
ScopeBean scopeInfo = new ScopeBean(ScopeProvider.instance.get());
if(!scopeInfo.is(Type.INFRASTRUCTURE))
return Response.status(status).entity(responseBean).build();
try{
UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager();
GCubeUser user = userManager.getUserByUsername(username);
responseBean.setSuccess(true);
responseBean.setMessage(null);
responseBean.setResult(user != null);
status = Status.OK;
}catch(Exception e){
logger.error("Unable to retrieve such information", e);
responseBean.setMessage(e.getMessage());
status = Status.INTERNAL_SERVER_ERROR;
}
return Response.status(status).entity(responseBean).build();
}
}