Commit c67052b0 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Added endpoint for retrieving member email addresses; CLI improvements

parent 909daf60
......@@ -22,6 +22,7 @@
</dependency>
</dependencies>
<build>
<finalName>gms-cli</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
......
package it.inaf.ia2.gms.cli;
import it.inaf.ia2.gms.client.GmsClient;
import it.inaf.ia2.gms.client.GmsClientBuilder;
import it.inaf.ia2.gms.client.model.Permission;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Properties;
public class CLI {
private final GmsClient client;
public static void main(String[] args) throws Exception {
new CLI().run(args);
new CLI(args).run();
}
private final String args[];
private int argIndex;
private String gmsBaseUrl;
private String rapBaseUrl;
private String clientId;
private String clientSecret;
private String token;
private GmsClient client;
private CLI(String... args) {
this.args = args;
}
private void run() throws Exception {
if (args.length < 2) {
displayUsage();
}
boolean commandParsed = false;
while (argIndex < args.length && !commandParsed) {
switch (args[argIndex]) {
case "--config-file":
loadConfigFromFile(new File(getNextArg()));
break;
case "--token-file":
loadTokenFromFile(new File(getNextArg()));
break;
case "--gms-url":
gmsBaseUrl = getNextArg();
break;
case "--rap-url":
rapBaseUrl = getNextArg();
break;
case "--client-id":
clientId = getNextArg();
break;
case "--client-secret":
clientSecret = getNextArg();
break;
default:
verifyConfigLoaded();
createClient();
parseCommand();
commandParsed = true;
break;
}
argIndex++;
}
}
private String getNextArg() {
if (argIndex + 1 == args.length) {
System.err.println("Missing value for option " + args[argIndex]);
System.exit(1);
}
return args[++argIndex];
}
private CLI() throws IOException {
private void verifyConfigLoaded() {
if (gmsBaseUrl == null) {
// Attempt reading gms.properties in current directory
loadConfigFromFile(new File("gms.properties"));
}
if (clientId == null && token == null) {
// Attempt loading token.txt in current directory
loadTokenFromFile(new File("token.txt"));
}
if (token != null && (clientSecret == null || rapBaseUrl == null)) {
System.err.println("Client secret and RAP base URL not configured");
System.exit(1);
}
}
private void createClient() {
GmsClientBuilder clientBuilder = new GmsClientBuilder()
.setGmsBaseUrl(gmsBaseUrl);
if (token != null) {
client = clientBuilder.build();
client.setAccessToken(token);
} else {
client = clientBuilder.setClientId(clientId)
.setClientSecret(clientSecret)
.setRapBaseUrl(rapBaseUrl)
.build();
}
}
private void loadConfigFromFile(File config) {
File config = new File("gms.properties");
if (!config.exists()) {
System.err.println("Unable to find the file gms.properties");
System.err.println("Config file " + config.getAbsolutePath() + " doesn't exist");
System.exit(1);
}
Properties properties = new Properties();
try (InputStream in = new FileInputStream(config)) {
properties.load(in);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
String baseUrl = (String) properties.get("base_url");
if (baseUrl == null) {
System.err.println("Missing base_url in gms.properties");
gmsBaseUrl = properties.getProperty("gms_url");
if (gmsBaseUrl == null) {
System.err.println("Missing gms_url in gms.properties");
System.exit(1);
}
rapBaseUrl = properties.getProperty("rap_url");
clientId = properties.getProperty("client_id");
clientSecret = properties.getProperty("client_secret");
}
private void loadTokenFromFile(File tokenFile) {
String token = (String) properties.get("token");
if (token == null) {
System.err.println("Missing token in gms.properties");
if (!tokenFile.exists()) {
System.err.println("Token file " + tokenFile.getAbsolutePath() + " doesn't exist");
System.exit(1);
}
client = new GmsClient(baseUrl).setAccessToken(token);
try (InputStream in = new FileInputStream(tokenFile)) {
java.util.Scanner s = new java.util.Scanner(in).useDelimiter("\\A");
token = s.next().trim();
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
public void run(String... args) throws Exception {
if (args.length < 2) {
displayUsage();
}
private void parseCommand() {
switch (args[0]) {
switch (args[argIndex]) {
case "create-group":
boolean leaf = false;
if (args.length > 1) {
leaf = Boolean.parseBoolean(args[2]);
if (argIndex + 2 < args.length) {
leaf = Boolean.parseBoolean(args[argIndex + 2]);
}
client.createGroup(args[1], leaf);
client.createGroup(args[argIndex + 1], leaf);
System.out.println("Group created");
break;
case "delete-group":
client.deleteGroup(args[1]);
client.deleteGroup(args[argIndex + 1]);
System.out.println("Group deleted");
break;
case "add-member":
if (args.length < 3) {
if (argIndex + 2 >= args.length) {
displayUsage();
}
client.addMember(args[1], args[2]);
client.addMember(args[argIndex + 1], args[argIndex + 2]);
System.out.println("Member added");
break;
case "remove-member":
if (args.length < 3) {
if (argIndex + 2 >= args.length) {
displayUsage();
}
client.removeMember(args[1], args[2]);
client.removeMember(args[argIndex + 1], args[argIndex + 2]);
System.out.println("Member removed");
break;
case "add-permission":
if (args.length < 4) {
if (argIndex + 3 >= args.length) {
displayUsage();
}
client.addPermission(args[1], args[2], Permission.valueOf(args[3]));
client.addPermission(args[argIndex + 1], args[argIndex + 2], Permission.valueOf(args[argIndex + 3]));
System.out.println("Permission added");
break;
case "delete-permission":
if (args.length < 4) {
if (argIndex + 2 >= args.length) {
displayUsage();
}
client.removePermission(args[1], args[2]);
client.removePermission(args[argIndex + 1], args[argIndex + 2]);
System.out.println("Permission removed");
break;
case "get-member-email-addresses":
Permission permission = null;
if (argIndex + 2 < args.length) {
permission = Permission.valueOf(args[argIndex + 2]);
}
List<String> addresses = client.getMemberEmailAddresses(args[argIndex + 1], permission);
for (String address : addresses) {
System.out.println(address);
}
break;
default:
displayUsage();
break;
......@@ -97,13 +209,20 @@ public class CLI {
}
private void displayUsage() {
System.out.println("java -jar gms-client.jar\n"
+ " create-group <name1.name2.name3> <leaf>\n"
System.out.println("gms-client\n"
+ " [--config-file <file>]\n"
+ " [--token-file <file>]\n"
+ " [--gms-url <url>]\n"
+ " [--rap-url <url>]\n"
+ " [--client-id <id>]\n"
+ " [--client-secret <secret>]\n"
+ " create-group <name1.name2.name3> [<leaf>]\n"
+ " delete-group <name1.name2.name3>\n"
+ " add-member <name1.name2.name3> <user_id>\n"
+ " remove-member <name1.name2.name3> <user_id>\n"
+ " add-permission <name1.name2.name3> <user_id> <permission>\n"
+ " delete-permission <name1.name2.name3> <user_id>");
+ " delete-permission <name1.name2.name3> <user_id>\n"
+ " get-member-email-addresses <name1.name2.name3> [<permission>]");
System.exit(0);
}
}
......@@ -7,6 +7,7 @@ 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.GetGroupPermissionsCall;
import it.inaf.ia2.gms.client.call.GetMemberEmailAddresses;
import it.inaf.ia2.gms.client.call.GetUserGroupsCall;
import it.inaf.ia2.gms.client.call.GetUserPermissionsCall;
import it.inaf.ia2.gms.client.call.ListGroupsCall;
......@@ -20,15 +21,10 @@ import java.util.Map;
public class GmsClient {
HttpClientWrapper httpClientWrapper;
private final HttpClientWrapper httpClientWrapper;
public GmsClient(String baseUrl) {
if (!baseUrl.endsWith("/")) {
baseUrl += "/";
}
httpClientWrapper = new HttpClientWrapper(baseUrl);
GmsClient(HttpClientWrapper httpClientWrapper) {
this.httpClientWrapper = httpClientWrapper;
}
public GmsClient setAccessToken(String accessToken) {
......@@ -83,4 +79,8 @@ public class GmsClient {
public void addInvitedRegistration(String token, String email, Map<String, Permission> groupsPermissions) {
new AddInvitedRegistrationCall(httpClientWrapper).addInvitedRegistration(token, email, groupsPermissions);
}
public List<String> getMemberEmailAddresses(String groupId, Permission permission) {
return new GetMemberEmailAddresses(httpClientWrapper).getMemberEmailAddresses(groupId, permission);
}
}
package it.inaf.ia2.gms.client;
import it.inaf.ia2.gms.client.call.HttpClientWrapper;
public class GmsClientBuilder {
private String gmsBaseUrl;
private String rapBaseUrl;
private String clientId;
private String clientSecret;
public GmsClientBuilder setGmsBaseUrl(String gmsBaseUrl) {
this.gmsBaseUrl = gmsBaseUrl;
return this;
}
public GmsClientBuilder setRapBaseUrl(String rapBaseUrl) {
this.rapBaseUrl = rapBaseUrl;
return this;
}
public GmsClientBuilder setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public GmsClientBuilder setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}
public GmsClient build() {
HttpClientWrapper clientWrapper = new HttpClientWrapper(gmsBaseUrl);
if (rapBaseUrl != null && clientId != null && clientSecret != null) {
clientWrapper.setRapBaseUrl(rapBaseUrl)
.setClientId(clientId).setClientSecret(clientSecret);
}
return new GmsClient(clientWrapper);
}
}
......@@ -27,13 +27,13 @@ public abstract class BaseGmsCall {
return clientWrapper.newHttpRequest(endpoint);
}
protected void logServerError(HttpRequest request, HttpResponse<String> response) {
protected static 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());
+ "\nServer response text is " + response.body());
}
protected void logServerErrorInputStream(HttpRequest request, HttpResponse<InputStream> response) {
protected static 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() : "";
......
package it.inaf.ia2.gms.client.call;
import it.inaf.ia2.gms.client.model.Permission;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class GetMemberEmailAddresses extends BaseGmsCall {
public GetMemberEmailAddresses(HttpClientWrapper clientWrapper) {
super(clientWrapper);
}
public List<String> getMemberEmailAddresses(String group, Permission permission) {
List<String> emailAddresses = new ArrayList<>();
String endpoint = "email/" + group;
if (permission != null) {
endpoint += "?permission=" + permission;
}
HttpRequest request = newHttpRequest(endpoint)
.header("Accept", "text/plain")
.GET()
.build();
return getClient().sendAsync(request, HttpResponse.BodyHandlers.ofInputStream())
.thenApply(response -> {
if (response.statusCode() == 200) {
return response.body();
}
logServerErrorInputStream(request, 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()) {
emailAddresses.add(line);
}
}
}
return emailAddresses;
}).join();
}
}
......@@ -4,12 +4,20 @@ import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.Builder;
import java.net.http.HttpResponse;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HttpClientWrapper {
private final String baseGmsUri;
private final HttpClient client;
private String rapBaseUrl;
private String clientId;
private String clientSecret;
private String accessToken;
public HttpClientWrapper(String baseGmsUri) {
......@@ -29,12 +37,68 @@ public class HttpClientWrapper {
return this;
}
public HttpClientWrapper setRapBaseUrl(String rapBaseUrl) {
if (!rapBaseUrl.endsWith("/")) {
rapBaseUrl += "/";
}
this.rapBaseUrl = rapBaseUrl;
return this;
}
public HttpClientWrapper setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public HttpClientWrapper setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}
Builder newHttpRequest(String endpoint) {
if (accessToken == null) {
accessToken = getAccessTokenFromClientCredentials();
}
return HttpRequest.newBuilder()
.uri(URI.create(baseGmsUri + endpoint))
.header("Authorization", "Bearer " + accessToken);
}
private String getAccessTokenFromClientCredentials() {
if (rapBaseUrl == null || clientId == null || clientSecret == null) {
throw new IllegalStateException("Access token is null and client credentials are not configured");
}
String basicAuthHeader = clientId + ":" + clientSecret;
HttpRequest tokenRequest = HttpRequest.newBuilder()
.uri(URI.create(rapBaseUrl + "auth/oauth2/token"))
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString(basicAuthHeader.getBytes()))
.header("Accept", "application/json")
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString("grant_type=client_credentials"))
.build();
return client.sendAsync(tokenRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(response -> {
if (response.statusCode() == 200) {
return getAccessTokenFromResponse(response.body());
}
BaseGmsCall.logServerError(tokenRequest, response);
throw new IllegalStateException("Unable to retrieve access token");
}).join();
}
protected String getAccessTokenFromResponse(String body) {
Pattern codePattern = Pattern.compile(".*\"access_token\":\\s*\"([^\"]+).*");
Matcher matcher = codePattern.matcher(body);
if (matcher.find()) {
return matcher.group(1);
}
throw new IllegalStateException("Unable to extract access token from body");
}
HttpClient getClient() {
return client;
}
......
......@@ -7,7 +7,6 @@ import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodySubscriber;
import java.net.http.HttpResponse.BodySubscribers;
......@@ -18,7 +17,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Flow;
import java.util.concurrent.Flow.Subscriber;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test;
......@@ -47,9 +45,9 @@ public class GmsClientTest {
httpClient = mock(HttpClient.class);
HttpClientWrapper clientWrapper = new MockedHttpClientWrapper(BASE_URL, httpClient);
clientWrapper.setAccessToken("foo");
client = new GmsClient(BASE_URL);
client.httpClientWrapper = clientWrapper;
client = new GmsClient(clientWrapper);
}
@Test
......
package it.inaf.ia2.gms.client.call;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class HttpClientWrapperTest {
@Test
public void testExtractAccessToken() {
String response = "{\"access_token\":\"TEST_TOKEN\",\"token_type\":\"Bearer\",\"expires_in\":3600}";
HttpClientWrapper clientWrapper = new HttpClientWrapper("http://localhost");
assertEquals("TEST_TOKEN", clientWrapper.getAccessTokenFromResponse(response));
}
}
......@@ -62,6 +62,7 @@
</dependencies>
<build>
<finalName>gms</finalName>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
......
......@@ -7,6 +7,7 @@ import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
import it.inaf.ia2.gms.manager.MembershipManager;
import it.inaf.ia2.gms.manager.PermissionsManager;
import it.inaf.ia2.gms.model.Permission;
import it.inaf.ia2.gms.model.RapUser;
import it.inaf.ia2.gms.model.response.UserPermission;
import it.inaf.ia2.gms.persistence.GroupsDAO;