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.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 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 GCUBE_TO_EXTENDED_PROFILE_MAP_WITH_VERIFIED_EMAIL = new Function() { @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 roles = roleManager.listRolesByUserAndGroup(user.getUserId(), groupManager.getGroupIdFromInfrastructureScope(scope)); List rolesNames = new ArrayList(); 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 usernames = new ArrayList(); try{ GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); long groupId = groupManager.getGroupIdFromInfrastructureScope(ScopeProvider.instance.get()); // first retrieve ids List 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 usernamesAndFullnames = new HashMap(); try{ GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); long groupId = groupManager.getGroupIdFromInfrastructureScope(ScopeProvider.instance.get()); // first retrieve ids List 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 usernames = new ArrayList(); try{ RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager(); long globalRoleId = roleManager.getRoleIdByName(roleName); if(globalRoleId > 0){ UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); List 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(); } private static final String REALM_NAME = "d4science"; private static final String clientId = "id.d4science.org"; private static final String SERVER_URL = "https://accounts.dev.d4science.org/auth"; private static final String client_secret = "09c26f24-3c65-4039-9fa0-e5cc4f4032cd"; /** * @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(); Keycloak keycloak; keycloak = KeycloakBuilder.builder() .serverUrl(SERVER_URL) .realm("d4science") .grantType(OAuth2Constants.CLIENT_CREDENTIALS) .clientId(clientId) // .clientSecret(client_secret).build(); List usernames = new ArrayList(); try { List users = searchByRole(keycloak, 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; } // try{ // GroupManager groupManager = GroupManagerWSBuilder.getInstance().getGroupManager(); // RoleManager roleManager = RoleManagerWSBuilder.getInstance().getRoleManager(); // long roleId = roleManager.getRoleIdByName(roleName); // if(roleId > 0){ // UserManager userManager = UserManagerWSBuilder.getInstance().getUserManager(); // List users = null; // long groupId = groupManager.getGroupIdFromInfrastructureScope(context); // // first check if for any reason this is a global role, then (if result is null or exception arises) check for site role // // Global role's users are retrieved much faster // try{ // if(GLOBAL_ROLES_ALLOWED_BY_LOCAL_CALL_METHOD.contains(roleName)){ // // TODO inconsistent value can be returned // users = userManager.listUsersByGlobalRole(roleId); // } // }catch(Exception globalExp){ // logger.warn("Failed while checking for global role... trying with local one", globalExp); // } // // if(users == null || users.isEmpty()){ // logger.debug("User list is still null/empty, checking for local information"); // users = userManager.listUsersByGroupAndRole(groupId, roleId); // } // // if(users != null){ // for (GCubeUser gCubeUser : users) { // usernames.add(gCubeUser.getUsername()); // } // } // responseBean.setResult(usernames); // responseBean.setSuccess(true); // }else{ // responseBean.setMessage("No 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(); } private static List searchByRole(Keycloak keycloak, String context, String roleName) { logger.info("Searching by role: {}", roleName); String clientIdContext = context.replace("/", "%2F") ; List clients = keycloak.realm(REALM_NAME) .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 users = keycloak.realm(REALM_NAME) .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(); } }