diff --git a/gms-client/gms-client-lib/pom.xml b/gms-client/gms-client-lib/pom.xml index 7ef51819587def10ba34fc840befcd09946c3a42..b05f489d35b7624ad100dc825bbad7eb95353b2d 100644 --- a/gms-client/gms-client-lib/pom.xml +++ b/gms-client/gms-client-lib/pom.xml @@ -7,21 +7,10 @@ <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <maven.compiler.source>1.8</maven.compiler.source> - <maven.compiler.target>1.8</maven.compiler.target> - <spring.version>5.1.8.RELEASE</spring.version> + <maven.compiler.source>12</maven.compiler.source> + <maven.compiler.target>12</maven.compiler.target> </properties> <dependencies> - <dependency> - <groupId>org.springframework</groupId> - <artifactId>spring-web</artifactId> - <version>${spring.version}</version> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - <version>2.9.9</version> - </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/GmsClient.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/GmsClient.java index ab11ceec1e47a2d8d5b0eb0157df9966cfe7a5b0..eb46bfc61e70059322575d3c0b6c42697124ca49 100644 --- a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/GmsClient.java +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/GmsClient.java @@ -1,134 +1,69 @@ package it.inaf.ia2.gms.client; -import it.inaf.ia2.gms.client.model.Group; -import it.inaf.ia2.gms.client.model.Member; +import it.inaf.ia2.gms.client.call.HttpClientWrapper; +import it.inaf.ia2.gms.client.call.AddMemberCall; +import it.inaf.ia2.gms.client.call.AddPermissionCall; +import it.inaf.ia2.gms.client.call.CreateGroupCall; +import it.inaf.ia2.gms.client.call.DeleteGroupCall; +import it.inaf.ia2.gms.client.call.GetUserGroupsCall; +import it.inaf.ia2.gms.client.call.ListGroupsCall; +import it.inaf.ia2.gms.client.call.RemoveMemberCall; +import it.inaf.ia2.gms.client.call.RemovePermissionCall; import it.inaf.ia2.gms.client.model.Permission; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; public class GmsClient { - private final String baseUrl; - private final String authHeader; - private RestTemplate restTemplate; + HttpClientWrapper httpClientWrapper; - public GmsClient(String baseUrl, String clientId, String clientSecret) { + public GmsClient(String baseUrl) { if (!baseUrl.endsWith("/")) { baseUrl += "/"; } - baseUrl += "ws/basic"; + baseUrl += "ws/jwt"; - this.baseUrl = baseUrl; - - String auth = clientId + ":" + clientSecret; - byte[] encodedAuth = Base64.getEncoder().encode( - auth.getBytes(StandardCharsets.UTF_8)); - authHeader = "Basic " + new String(encodedAuth); - - restTemplate = new RestTemplate(); + httpClientWrapper = new HttpClientWrapper(baseUrl); } - /** - * For testing purpose. - */ - protected void setRestTemplate(RestTemplate restTemplate) { - this.restTemplate = restTemplate; + public GmsClient setAccessToken(String accessToken) { + httpClientWrapper.setAccessToken(accessToken); + return this; } - public Group addGroup(List<String> names) { - - String url = UriComponentsBuilder.fromHttpUrl(baseUrl) - .pathSegment("group") - .toUriString(); - - HttpEntity<List<String>> httpEntity = getEntity(names); - - return restTemplate.exchange(url, HttpMethod.POST, httpEntity, Group.class).getBody(); + public List<String> getMyGroups(String prefix) { + return new GetUserGroupsCall(httpClientWrapper).getUserGroups(prefix); } - public void removeGroup(List<String> names) { - - String url = UriComponentsBuilder.fromHttpUrl(baseUrl) - .pathSegment("group") - .queryParam("names", names.toArray()) - .toUriString(); - - restTemplate.exchange(url, HttpMethod.DELETE, getEntity(), Void.class); + public List<String> listGroups(String prefix) { + return new ListGroupsCall(httpClientWrapper).listGroups(prefix); } - public Member addMember(List<String> names, String userId) { - - String url = UriComponentsBuilder.fromHttpUrl(baseUrl) - .pathSegment("member") - .toUriString(); - - Map<String, Object> params = new HashMap<>(); - params.put("names", names); - params.put("userId", userId); - HttpEntity<Map<String, Object>> httpEntity = getEntity(params); - - return restTemplate.exchange(url, HttpMethod.POST, httpEntity, Member.class).getBody(); + public List<String> getUserGroups(String userId, String prefix) { + return new GetUserGroupsCall(httpClientWrapper).getUserGroups(userId, prefix); } - public void removeMember(List<String> names, String userId) { - - String url = UriComponentsBuilder.fromHttpUrl(baseUrl) - .pathSegment("member") - .queryParam("names", names.toArray()) - .queryParam("userId", userId) - .toUriString(); - - restTemplate.exchange(url, HttpMethod.DELETE, getEntity(), Void.class); + public void createGroup(String completeGroupName, boolean leaf) { + new CreateGroupCall(httpClientWrapper).createGroup(completeGroupName, leaf); } - public Permission addPermission(List<String> names, String userId, String permission) { - - String url = UriComponentsBuilder.fromHttpUrl(baseUrl) - .pathSegment("permission") - .toUriString(); - - Map<String, Object> params = new HashMap<>(); - params.put("names", names); - params.put("userId", userId); - params.put("permission", permission); - HttpEntity<Map<String, Object>> httpEntity = getEntity(params); - - return restTemplate.exchange(url, HttpMethod.POST, httpEntity, Permission.class).getBody(); + public void deleteGroup(String completeGroupName) { + new DeleteGroupCall(httpClientWrapper).deleteGroup(completeGroupName); } - public void removePermission(List<String> names, String userId) { - - String url = UriComponentsBuilder.fromHttpUrl(baseUrl) - .pathSegment("permission") - .queryParam("names", names.toArray()) - .queryParam("userId", userId) - .toUriString(); - - restTemplate.exchange(url, HttpMethod.DELETE, getEntity(), Void.class); + public void addMember(String completeGroupName, String userId) { + new AddMemberCall(httpClientWrapper).addMember(completeGroupName, userId); } - private HttpEntity<?> getEntity() { - return new HttpEntity<>(getHeaders()); + public void removeMember(String completeGroupName, String userId) { + new RemoveMemberCall(httpClientWrapper).removeMember(completeGroupName, userId); } - private <T> HttpEntity<T> getEntity(T body) { - return new HttpEntity<>(body, getHeaders()); + public void addPermission(String completeGroupName, String userId, Permission permission) { + new AddPermissionCall(httpClientWrapper).addPermission(completeGroupName, userId, permission); } - private HttpHeaders getHeaders() { - return new HttpHeaders() { - { - set("Authorization", authHeader); - } - }; + public void removePermission(String completeGroupName, String userId) { + new RemovePermissionCall(httpClientWrapper).removePermission(completeGroupName, userId); } } diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/AddMemberCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/AddMemberCall.java new file mode 100644 index 0000000000000000000000000000000000000000..42abfe7603e7fbf43c72d5916479994e9fe0f668 --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/AddMemberCall.java @@ -0,0 +1,34 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; + +public class AddMemberCall extends BaseGmsCall { + + public AddMemberCall(HttpClientWrapper clientWrapper) { + super(clientWrapper); + } + + public boolean addMember(String completeGroupName, String userId) { + + String endpoint = "membership"; + if (completeGroupName != null && !completeGroupName.isBlank()) { + endpoint += "/" + completeGroupName; + } + + HttpRequest groupsRequest = newHttpRequest(endpoint) + .header("Accept", "text/plain") + .POST(BodyPublishers.ofString("user_id=" + userId)) + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 200) { + return true; + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to add member to group"); + }).join(); + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/AddPermissionCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/AddPermissionCall.java new file mode 100644 index 0000000000000000000000000000000000000000..99c5d8095179399df034be8c717befbf5daab431 --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/AddPermissionCall.java @@ -0,0 +1,39 @@ +package it.inaf.ia2.gms.client.call; + +import it.inaf.ia2.gms.client.model.Permission; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublisher; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; + +public class AddPermissionCall extends BaseGmsCall { + + public AddPermissionCall(HttpClientWrapper clientWrapper) { + super(clientWrapper); + } + + public boolean addPermission(String completeGroupName, String userId, Permission permission) { + + String endpoint = "permission"; + if (completeGroupName != null && !completeGroupName.isBlank()) { + endpoint += "/" + completeGroupName; + } + + BodyPublisher requestBody = BodyPublishers.ofString( + "user_id=" + userId + "&permission=" + permission); + + HttpRequest groupsRequest = newHttpRequest(endpoint) + .header("Accept", "text/plain") + .POST(requestBody) + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 200) { + return true; + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to add permission"); + }).join(); + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/BaseGmsCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/BaseGmsCall.java new file mode 100644 index 0000000000000000000000000000000000000000..6f0316068c3d59af7bf7a2150d2cbe503c07e081 --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/BaseGmsCall.java @@ -0,0 +1,45 @@ +package it.inaf.ia2.gms.client.call; + +import java.io.InputStream; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.Builder; +import java.net.http.HttpResponse; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +public abstract class BaseGmsCall { + + private static final Logger LOGGER = Logger.getLogger(BaseGmsCall.class.getName()); + + protected final HttpClientWrapper clientWrapper; + + public BaseGmsCall(HttpClientWrapper clientWrapper) { + this.clientWrapper = clientWrapper; + } + + protected HttpClient getClient() { + return clientWrapper.getClient(); + } + + protected Builder newHttpRequest(String endpoint) { + return clientWrapper.newHttpRequest(endpoint); + } + + protected void logServerError(HttpRequest request, HttpResponse<String> response) { + LOGGER.log(Level.SEVERE, () -> "Error while reading " + request.uri() + + "\nServer response status code is " + response.statusCode() + + "\nAServer response text is " + response.body()); + } + + protected void logServerErrorInputStream(HttpRequest request, HttpResponse<InputStream> response) { + LOGGER.log(Level.SEVERE, () -> { + Scanner s = new Scanner(response.body()).useDelimiter("\\A"); + String responseBody = s.hasNext() ? s.next() : ""; + return "Error while reading " + request.uri() + + "\nServer response status code is " + response.statusCode() + + "\nServer response text is " + responseBody; + }); + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/CreateGroupCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/CreateGroupCall.java new file mode 100644 index 0000000000000000000000000000000000000000..9636232360c36160ae46568c6fb84edaa4ca3fce --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/CreateGroupCall.java @@ -0,0 +1,29 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublishers; +import java.net.http.HttpResponse; + +public class CreateGroupCall extends BaseGmsCall { + + public CreateGroupCall(HttpClientWrapper clientWrapper) { + super(clientWrapper); + } + + public boolean createGroup(String completeGroupName, boolean leaf) { + + HttpRequest groupsRequest = newHttpRequest(completeGroupName) + .header("Accept", "text/plain") + .POST(BodyPublishers.ofString("leaf=" + leaf)) + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 200) { + return true; + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to create group"); + }).join(); + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/DeleteGroupCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/DeleteGroupCall.java new file mode 100644 index 0000000000000000000000000000000000000000..56b7b9893ccbe30ba3612a8d5bdc9d3a99601075 --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/DeleteGroupCall.java @@ -0,0 +1,28 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class DeleteGroupCall extends BaseGmsCall { + + public DeleteGroupCall(HttpClientWrapper clientWrapper) { + super(clientWrapper); + } + + public boolean deleteGroup(String completeGroupName) { + + HttpRequest groupsRequest = newHttpRequest(completeGroupName) + .header("Accept", "text/plain") + .DELETE() + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 204) { + return true; + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to delete group"); + }).join(); + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/GetUserGroupsCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/GetUserGroupsCall.java new file mode 100644 index 0000000000000000000000000000000000000000..57ab0ffe8b6ba2e1d88e6ca10ab255a59c05a802 --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/GetUserGroupsCall.java @@ -0,0 +1,91 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class GetUserGroupsCall extends BaseGmsCall { + + public GetUserGroupsCall(HttpClientWrapper clientWrapper) { + super(clientWrapper); + } + + /** + * Returns the groups the user belongs to. If a groupsPrefix (parent group) + * is specified, the prefix is removed from the list. + */ + public List<String> getUserGroups(String prefix) { + + List<String> groups = new ArrayList<>(); + + HttpRequest groupsRequest = newHttpRequest("search") + .header("Accept", "text/plain") + .GET() + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 200) { + return response.body(); + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to retrieve groups"); + }) + .thenApply(inputStream -> { + try (Scanner scan = new Scanner(inputStream)) { + while (scan.hasNextLine()) { + String line = scan.nextLine(); + if (!line.isEmpty()) { + if (prefix == null || prefix.isEmpty()) { + groups.add(line); + } else { + if (line.startsWith(prefix)) { + line = line.substring(prefix.length()); + groups.add(line); + } + } + } + } + } + return groups; + }).join(); + } + + public List<String> getUserGroups(String userId, String prefix) { + + List<String> groups = new ArrayList<>(); + + String endpoint = "membership"; + if (prefix != null && !prefix.isBlank()) { + endpoint += "/" + prefix; + } + endpoint += "?user_id=" + userId; + + HttpRequest groupsRequest = newHttpRequest(endpoint) + .header("Accept", "text/plain") + .GET() + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 200) { + return response.body(); + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to retrieve groups"); + }) + .thenApply(inputStream -> { + try (Scanner scan = new Scanner(inputStream)) { + while (scan.hasNextLine()) { + String line = scan.nextLine(); + if (!line.isEmpty()) { + groups.add(line); + } + } + } + return groups; + }).join(); + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/HttpClientWrapper.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/HttpClientWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..7bbf781a7ccc64f05bee33c89cbb874c12194daf --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/HttpClientWrapper.java @@ -0,0 +1,41 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.Builder; + +public class HttpClientWrapper { + + private final String baseGmsUri; + private final HttpClient client; + + private String accessToken; + + public HttpClientWrapper(String baseGmsUri) { + String uri = baseGmsUri; + if (!uri.endsWith("/")) { + uri += "/"; + } + this.baseGmsUri = uri; + + this.client = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.ALWAYS) + .build(); + } + + public HttpClientWrapper setAccessToken(String accessToken) { + this.accessToken = accessToken; + return this; + } + + Builder newHttpRequest(String endpoint) { + return HttpRequest.newBuilder() + .uri(URI.create(baseGmsUri + endpoint)) + .header("Authorization", "Bearer " + accessToken); + } + + HttpClient getClient() { + return client; + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/ListGroupsCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/ListGroupsCall.java new file mode 100644 index 0000000000000000000000000000000000000000..7170ebaac74285e8be472d82762e08888b3e40ca --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/ListGroupsCall.java @@ -0,0 +1,54 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +public class ListGroupsCall extends BaseGmsCall { + + public ListGroupsCall(HttpClientWrapper clientWrapper) { + super(clientWrapper); + } + + /** + * Returns the list of the groups in a given parent group (if the user has + * the privileges to see that information). The prefix is removed by the + * service. + */ + public List<String> listGroups(String prefix) { + + List<String> groups = new ArrayList<>(); + + String uri = "list"; + if (prefix != null && !prefix.isBlank()) { + uri += "/" + prefix; + } + + HttpRequest groupsRequest = newHttpRequest(uri) + .header("Accept", "text/plain") + .GET() + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 200) { + return response.body(); + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to list groups"); + }) + .thenApply(inputStream -> { + try (Scanner scan = new Scanner(inputStream)) { + while (scan.hasNextLine()) { + String line = scan.nextLine(); + if (!line.isEmpty()) { + groups.add(line); + } + } + } + return groups; + }).join(); + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/RemoveMemberCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/RemoveMemberCall.java new file mode 100644 index 0000000000000000000000000000000000000000..cc5b01a3ed2e8ec9c4f70c6cd32f29383c8dc09e --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/RemoveMemberCall.java @@ -0,0 +1,34 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class RemoveMemberCall extends BaseGmsCall { + + public RemoveMemberCall(HttpClientWrapper clientWrapper) { + super(clientWrapper); + } + + public boolean removeMember(String completeGroupName, String userId) { + + String endpoint = "membership"; + if (completeGroupName != null && !completeGroupName.isBlank()) { + endpoint += "/" + completeGroupName; + } + endpoint += "?user_id=" + userId; + + HttpRequest groupsRequest = newHttpRequest(endpoint) + .header("Accept", "text/plain") + .DELETE() + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 204) { + return true; + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to remove member from group"); + }).join(); + } +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/RemovePermissionCall.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/RemovePermissionCall.java new file mode 100644 index 0000000000000000000000000000000000000000..7f17e76e96cc34875a6b0447e18afa181404f783 --- /dev/null +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/call/RemovePermissionCall.java @@ -0,0 +1,35 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class RemovePermissionCall extends BaseGmsCall { + + public RemovePermissionCall(HttpClientWrapper clientWrapper) { + super(clientWrapper); + } + + public boolean removePermission(String completeGroupName, String userId) { + + String endpoint = "permission"; + if (completeGroupName != null && !completeGroupName.isBlank()) { + endpoint += "/" + completeGroupName; + } + endpoint += "?user_id=" + userId; + + HttpRequest groupsRequest = newHttpRequest(endpoint) + .header("Accept", "text/plain") + .DELETE() + .build(); + + return getClient().sendAsync(groupsRequest, HttpResponse.BodyHandlers.ofInputStream()) + .thenApply(response -> { + if (response.statusCode() == 204) { + return true; + } + logServerErrorInputStream(groupsRequest, response); + throw new IllegalStateException("Unable to remove permission"); + }).join(); + } + +} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Group.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Group.java deleted file mode 100644 index d0b95fdb5190aaf1e1817927944c8b910661283d..0000000000000000000000000000000000000000 --- a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Group.java +++ /dev/null @@ -1,41 +0,0 @@ -package it.inaf.ia2.gms.client.model; - -public class Group { - - private String id; - private String name; - private String path; - private String parentPath; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } - - public String getParentPath() { - return parentPath; - } - - public void setParentPath(String parentPath) { - this.parentPath = parentPath; - } -} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Member.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Member.java deleted file mode 100644 index 37b0096c79c7e455f6f65937dcb2d8aa629964e5..0000000000000000000000000000000000000000 --- a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Member.java +++ /dev/null @@ -1,23 +0,0 @@ -package it.inaf.ia2.gms.client.model; - -public class Member { - - private String groupId; - private String userId; - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } -} diff --git a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Permission.java b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Permission.java index bb809c4ee8a4bfe12039c24caf6b2880a2bf0636..71e708ca3d00a3862d9afd1bca83fd865f0717b0 100644 --- a/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Permission.java +++ b/gms-client/gms-client-lib/src/main/java/it/inaf/ia2/gms/client/model/Permission.java @@ -1,32 +1,9 @@ package it.inaf.ia2.gms.client.model; -public class Permission { +public enum Permission { - private String groupId; - private String userId; - private String permission; - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getPermission() { - return permission; - } - - public void setPermission(String permission) { - this.permission = permission; - } + ADMIN, + MANAGE_MEMBERS, + VIEW_MEMBERS, + TRAVERSE; } diff --git a/gms-client/gms-client-lib/src/test/java/it/inaf/ia2/gms/client/GmsClientTest.java b/gms-client/gms-client-lib/src/test/java/it/inaf/ia2/gms/client/GmsClientTest.java index e0eeb8d699633e669f776f791d3647ed31db8609..63233fb8cdb96f72926dc772e34ada411574b135 100644 --- a/gms-client/gms-client-lib/src/test/java/it/inaf/ia2/gms/client/GmsClientTest.java +++ b/gms-client/gms-client-lib/src/test/java/it/inaf/ia2/gms/client/GmsClientTest.java @@ -1,161 +1,182 @@ package it.inaf.ia2.gms.client; -import it.inaf.ia2.gms.client.model.Group; -import it.inaf.ia2.gms.client.model.Member; +import it.inaf.ia2.gms.client.call.HttpClientWrapper; +import it.inaf.ia2.gms.client.call.MockedHttpClientWrapper; import it.inaf.ia2.gms.client.model.Permission; -import java.util.Arrays; -import java.util.HashMap; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Map; +import java.util.concurrent.CompletableFuture; import static org.junit.Assert.assertEquals; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatcher; +import org.mockito.ArgumentMatchers; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestTemplate; @RunWith(MockitoJUnitRunner.class) public class GmsClientTest { private static final String BASE_URL = "http://base-url"; - private RestTemplate restTemplate; + private HttpClient httpClient; private GmsClient client; @Before public void setUp() { - restTemplate = mock(RestTemplate.class); + httpClient = mock(HttpClient.class); - client = new GmsClient(BASE_URL, "test", "test"); - client.setRestTemplate(restTemplate); + HttpClientWrapper clientWrapper = new MockedHttpClientWrapper(BASE_URL, httpClient); - when(restTemplate.exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), any(Class.class))) - .thenReturn(new ResponseEntity<>(HttpStatus.I_AM_A_TEAPOT)); + client = new GmsClient(BASE_URL); + client.httpClientWrapper = clientWrapper; } @Test - public void testCreateGroup() { + public void testGetMyGroups() { + + String body = "LBT.INAF\n" + + "LBT.AZ"; - List<String> names = Arrays.asList("LBT", "INAF"); + CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(200, body)); - client.addGroup(names); + when(httpClient.sendAsync(any(), any())).thenReturn(response); + List<String> groups = client.getMyGroups("LBT."); - ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class); - verify(restTemplate, times(1)).exchange(eq(BASE_URL + "/ws/group"), - eq(HttpMethod.POST), entityCaptor.capture(), eq(Group.class)); + verify(httpClient, times(1)).sendAsync(endpointEq("GET", "search"), any()); - HttpEntity<?> entity = entityCaptor.getValue(); - verifyAuthHeaders(entity); - verifyBody(entity, names); + assertEquals(2, groups.size()); + assertEquals("INAF", groups.get(0)); + assertEquals("AZ", groups.get(1)); } @Test - public void testDeleteGroup() { + public void testListGroups() { + + String body = "INAF\n" + + "AZ"; - List<String> names = Arrays.asList("LBT", "INAF"); + CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(200, body)); - client.removeGroup(names); + when(httpClient.sendAsync(any(), any())).thenReturn(response); + List<String> groups = client.listGroups("LBT."); - ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class); - verify(restTemplate, times(1)).exchange(eq(BASE_URL + "/ws/group?names=LBT&names=INAF"), - eq(HttpMethod.DELETE), entityCaptor.capture(), eq(Void.class)); + verify(httpClient, times(1)).sendAsync(endpointEq("GET", "list/LBT."), any()); - HttpEntity<?> entity = entityCaptor.getValue(); - verifyAuthHeaders(entity); + assertEquals(2, groups.size()); + assertEquals("INAF", groups.get(0)); + assertEquals("AZ", groups.get(1)); } @Test - public void testAddMember() { + public void testCreateGroup() { - List<String> names = Arrays.asList("LBT", "INAF"); + CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(200)); - client.addMember(names, "user_id"); + when(httpClient.sendAsync(any(), any())).thenReturn(response); + client.createGroup("LBT.INAF", false); - ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class); - verify(restTemplate, times(1)).exchange(eq(BASE_URL + "/ws/member"), - eq(HttpMethod.POST), entityCaptor.capture(), eq(Member.class)); + verify(httpClient, times(1)).sendAsync(endpointEq("POST", "LBT.INAF"), any()); + } - HttpEntity<?> entity = entityCaptor.getValue(); - verifyAuthHeaders(entity); + @Test + public void testDeleteGroup() { + + CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(204)); - Map<String, Object> expectedBody = new HashMap<>(); - expectedBody.put("names", names); - expectedBody.put("userId", "user_id"); + when(httpClient.sendAsync(any(), any())).thenReturn(response); + client.deleteGroup("LBT.INAF"); - verifyBody(entity, expectedBody); + verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "LBT.INAF"), any()); } @Test - public void testRemoveMember() { - - List<String> names = Arrays.asList("LBT", "INAF"); + public void testAddMember() { - client.removeMember(names, "user_id"); + CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(200)); - ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class); - verify(restTemplate, times(1)).exchange(eq(BASE_URL + "/ws/member?names=LBT&names=INAF&userId=user_id"), - eq(HttpMethod.DELETE), entityCaptor.capture(), eq(Void.class)); + when(httpClient.sendAsync(any(), any())).thenReturn(response); + client.addMember("LBT.INAF", "user"); - HttpEntity<?> entity = entityCaptor.getValue(); - verifyAuthHeaders(entity); + verify(httpClient, times(1)).sendAsync(endpointEq("POST", "membership/LBT.INAF"), any()); } @Test - public void testAddPermission() { + public void testRemoveMember() { - List<String> names = Arrays.asList("LBT", "INAF"); + CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(204)); - client.addPermission(names, "user_id", "ADMIN"); + when(httpClient.sendAsync(any(), any())).thenReturn(response); + client.removeMember("LBT.INAF", "user"); - ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class); - verify(restTemplate, times(1)).exchange(eq(BASE_URL + "/ws/permission"), - eq(HttpMethod.POST), entityCaptor.capture(), eq(Permission.class)); + verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "membership/LBT.INAF?user_id=user"), any()); + } - HttpEntity<?> entity = entityCaptor.getValue(); - verifyAuthHeaders(entity); + @Test + public void testAddPermission() { + + CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(200)); - Map<String, Object> expectedBody = new HashMap<>(); - expectedBody.put("names", names); - expectedBody.put("userId", "user_id"); - expectedBody.put("permission", "ADMIN"); + when(httpClient.sendAsync(any(), any())).thenReturn(response); + client.addPermission("LBT.INAF", "user", Permission.ADMIN); - verifyBody(entity, expectedBody); + verify(httpClient, times(1)).sendAsync(endpointEq("POST", "permission/LBT.INAF"), any()); } @Test - public void testDeletePermission() { + public void testRemovePermission() { - List<String> names = Arrays.asList("LBT", "INAF"); + CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(204)); - client.removePermission(names, "user_id"); + when(httpClient.sendAsync(any(), any())).thenReturn(response); + client.removePermission("LBT.INAF", "user"); - ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class); - verify(restTemplate, times(1)).exchange(eq(BASE_URL + "/ws/permission?names=LBT&names=INAF&userId=user_id"), - eq(HttpMethod.DELETE), entityCaptor.capture(), eq(Void.class)); + verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "permission/LBT.INAF?user_id=user"), any()); + } - HttpEntity<?> entity = entityCaptor.getValue(); - verifyAuthHeaders(entity); + private HttpResponse getMockedResponse(int statusCode, String body) { + HttpResponse response = getMockedResponse(statusCode); + InputStream in = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); + when(response.body()).thenReturn(in); + return response; } - private void verifyAuthHeaders(HttpEntity<?> entity) {// - String authHeader = entity.getHeaders().getFirst("Authorization"); - assertEquals("Basic dGVzdDp0ZXN0", authHeader); + private HttpResponse getMockedResponse(int statusCode) { + HttpResponse response = mock(HttpResponse.class); + when(response.statusCode()).thenReturn(statusCode); + return response; } - private <T> void verifyBody(HttpEntity<?> entity, T body) { - assertEquals(entity.getBody(), body); + private HttpRequest endpointEq(String expectedMethod, String expectedEndpoint) { + return ArgumentMatchers.argThat(endpointEqArgumentMatcher(expectedMethod, expectedEndpoint)); + } + + private ArgumentMatcher<HttpRequest> endpointEqArgumentMatcher(String expectedMethod, String expectedEndpoint) { + + return new ArgumentMatcher<HttpRequest>() { + + private final String expectedUri = BASE_URL + "/" + expectedEndpoint; + + @Override + public boolean matches(HttpRequest request) { + return expectedMethod.equals(request.method()) && expectedUri.equals(request.uri().toString()); + } + + @Override + public String toString() { + return expectedMethod + " " + expectedUri; + } + }; } } diff --git a/gms-client/gms-client-lib/src/test/java/it/inaf/ia2/gms/client/call/MockedHttpClientWrapper.java b/gms-client/gms-client-lib/src/test/java/it/inaf/ia2/gms/client/call/MockedHttpClientWrapper.java new file mode 100644 index 0000000000000000000000000000000000000000..d287f9039f03d5c1c156ff2b4f91362c09a5dedc --- /dev/null +++ b/gms-client/gms-client-lib/src/test/java/it/inaf/ia2/gms/client/call/MockedHttpClientWrapper.java @@ -0,0 +1,18 @@ +package it.inaf.ia2.gms.client.call; + +import java.net.http.HttpClient; + +public class MockedHttpClientWrapper extends HttpClientWrapper { + + private final HttpClient mockedClient; + + public MockedHttpClientWrapper(String baseGmsUri, HttpClient mockedClient) { + super(baseGmsUri); + this.mockedClient = mockedClient; + } + + @Override + HttpClient getClient() { + return mockedClient; + } +} diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java index c3e6d98d905f6d329a0e01d0617db6ec62255cd8..f338cfefd0179acd487c70921dd8123a44dcc86c 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/JWTWebServiceController.java @@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -174,6 +175,13 @@ public class JWTWebServiceController { } } + @DeleteMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE) + public void deleteGroup(@PathVariable("group") String groupParam, HttpServletResponse response) { + GroupEntity group = getGroupFromNames(extractGroupNames(groupParam)); + groupsDAO.deleteGroupById(group.getId()); + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + @GetMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE) public void getMembership(@PathVariable("group") Optional<String> group, @RequestParam("user_id") String userId, HttpServletResponse response) throws IOException { @@ -202,6 +210,16 @@ public class JWTWebServiceController { membershipManager.addMember(groupEntity, targetUserId); } + @DeleteMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE) + public void removeMember(@PathVariable("group") Optional<String> group, @RequestParam("user_id") String userId, + HttpServletRequest request, HttpServletResponse response) throws IOException { + + GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group)); + membershipManager.removeMember(groupEntity, userId); + + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + @PostMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE) public void addPermission(@PathVariable("group") Optional<String> groupNames, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -221,6 +239,16 @@ public class JWTWebServiceController { permissionsManager.addPermission(groupEntity, targetUserId, Permission.valueOf(permissionParam)); } + @DeleteMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE) + public void removePermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId, + HttpServletRequest request, HttpServletResponse response) throws IOException { + + GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames)); + permissionsManager.removePermission(groupEntity, userId); + + response.setStatus(HttpServletResponse.SC_NO_CONTENT); + } + private GroupEntity getGroupFromNames(List<String> groupNames) { if (groupNames.isEmpty()) { return getRoot();