Skip to content
Snippets Groups Projects
Commit 82262c27 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Implemented logic for retrieving list of groups and users from GMS and RAP and...

Implemented logic for retrieving list of groups and users from GMS and RAP and updating the groupRead and groupWrite properties creating the single-user groups when needed
parent ccca519e
No related branches found
No related tags found
No related merge requests found
Showing
with 534 additions and 0 deletions
...@@ -55,6 +55,11 @@ ...@@ -55,6 +55,11 @@
<artifactId>mockito-inline</artifactId> <artifactId>mockito-inline</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>gms-client</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>
......
package it.inaf.ia2.vospace.ui; package it.inaf.ia2.vospace.ui;
import it.inaf.ia2.aa.AuthConfig;
import it.inaf.ia2.aa.LoginFilter; import it.inaf.ia2.aa.LoginFilter;
import it.inaf.ia2.aa.ServiceLocator; import it.inaf.ia2.aa.ServiceLocator;
import it.inaf.ia2.aa.UserManager; import it.inaf.ia2.aa.UserManager;
import it.inaf.ia2.gms.client.GmsClient;
import it.inaf.ia2.rap.client.ClientCredentialsRapClient;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
...@@ -61,4 +64,21 @@ public class VOSpaceUiApplication { ...@@ -61,4 +64,21 @@ public class VOSpaceUiApplication {
public RestTemplate restTemplate() { public RestTemplate restTemplate() {
return new RestTemplate(); return new RestTemplate();
} }
@Bean
public ClientCredentialsRapClient gmsRapClient() {
AuthConfig config = ServiceLocator.getInstance().getConfig();
ClientCredentialsRapClient rapClient = new ClientCredentialsRapClient(config.getRapBaseUri());
rapClient.setClientId(config.getClientId());
rapClient.setClientSecret(config.getClientSecret());
return rapClient;
}
@Bean
public GmsClient gmsClient() {
String gmsBaseUri = ServiceLocator.getInstance().getConfig().getGmsUri();
return new GmsClient(gmsBaseUri);
}
} }
...@@ -126,6 +126,19 @@ public class VOSpaceClient { ...@@ -126,6 +126,19 @@ public class VOSpaceClient {
call(request, BodyHandlers.ofInputStream(), 200, res -> null); call(request, BodyHandlers.ofInputStream(), 200, res -> null);
} }
public Node setNode(Node node) {
String path = node.getUri().substring(("vos://" + authority).length());
HttpRequest request = getRequest("/nodes" + urlEncodePath(path))
.header("Accept", useJson ? "application/json" : "text/xml")
.header("Content-Type", useJson ? "application/json" : "text/xml")
.POST(HttpRequest.BodyPublishers.ofString(marshal(node)))
.build();
return call(request, BodyHandlers.ofInputStream(), 200, res -> unmarshal(res, Node.class));
}
public List<Job> getJobs() { public List<Job> getJobs() {
HttpRequest request = getRequest("/transfers?direction=pullToVoSpace") HttpRequest request = getRequest("/transfers?direction=pullToVoSpace")
......
package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.vospace.ui.data.ShareRequest;
import it.inaf.ia2.vospace.ui.data.SharingInfo;
import it.inaf.ia2.vospace.ui.service.SharingService;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SharingController {
@Autowired
private SharingService sharingService;
@GetMapping(value = "/sharing", produces = MediaType.APPLICATION_JSON_VALUE)
public SharingInfo getSharingInfo() {
return sharingService.getSharingInfo();
}
@PostMapping(value = "/sharing", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> setNodeGroups(@Valid @RequestBody ShareRequest shareRequest) {
sharingService.setNodeGroups(shareRequest);
return ResponseEntity.ok().build();
}
}
package it.inaf.ia2.vospace.ui.data;
import java.util.List;
public class ShareRequest {
private String path;
private List<String> groupRead;
private List<String> groupWrite;
private List<String> userRead;
private List<String> userWrite;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public List<String> getGroupRead() {
return groupRead;
}
public void setGroupRead(List<String> groupRead) {
this.groupRead = groupRead;
}
public List<String> getGroupWrite() {
return groupWrite;
}
public void setGroupWrite(List<String> groupWrite) {
this.groupWrite = groupWrite;
}
public List<String> getUserRead() {
return userRead;
}
public void setUserRead(List<String> userRead) {
this.userRead = userRead;
}
public List<String> getUserWrite() {
return userWrite;
}
public void setUserWrite(List<String> userWrite) {
this.userWrite = userWrite;
}
}
package it.inaf.ia2.vospace.ui.data;
import java.util.List;
public class SharingInfo {
private List<String> people;
private List<String> groups;
public List<String> getPeople() {
return people;
}
public void setPeople(List<String> people) {
this.people = people;
}
public List<String> getGroups() {
return groups;
}
public void setGroups(List<String> groups) {
this.groups = groups;
}
}
package it.inaf.ia2.vospace.ui.service;
import it.inaf.ia2.gms.client.GmsClient;
import it.inaf.ia2.gms.client.model.Permission;
import it.inaf.ia2.rap.client.ClientCredentialsRapClient;
import it.inaf.ia2.rap.data.Identity;
import it.inaf.ia2.rap.data.IdentityType;
import it.inaf.ia2.rap.data.RapUser;
import it.inaf.ia2.rap.data.TokenContext;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.ShareRequest;
import it.inaf.ia2.vospace.ui.data.SharingInfo;
import it.inaf.ia2.vospace.ui.exception.BadRequestException;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class SharingService {
private static final Logger LOG = LoggerFactory.getLogger(SharingService.class);
@Value("${trusted.eppn.scope}")
protected String trustedEppnScope;
private final Set<String> existingGroups = new CopyOnWriteArraySet<>();
private final Set<String> existingPeopleGroups = new CopyOnWriteArraySet<>();
private final Map<String, String> existingUsers = new ConcurrentHashMap<>();
private final GmsClient gmsClient;
private final ClientCredentialsRapClient rapClient;
private final VOSpaceClient vospaceClient;
private TokenContext tokenContext;
private Date lastUpdate;
@Autowired
private HttpServletRequest request;
@Autowired
public SharingService(GmsClient gmsClient, ClientCredentialsRapClient gmsRapClient, VOSpaceClient vospaceClient) {
this.gmsClient = gmsClient;
this.rapClient = gmsRapClient;
this.vospaceClient = vospaceClient;
}
public SharingInfo getSharingInfo() {
loadInfoFromServices();
SharingInfo sharingInfo = new SharingInfo();
Set<String> people = new HashSet<>(existingUsers.values());
people.addAll(existingPeopleGroups);
sharingInfo.setPeople(new ArrayList<>(people));
Collections.sort(sharingInfo.getPeople());
sharingInfo.setGroups(new ArrayList<>(existingGroups));
Collections.sort(sharingInfo.getGroups());
return sharingInfo;
}
public void setNodeGroups(ShareRequest shareRequest) {
loadInfoFromServices();
createPeopleGroupIfNeeded(shareRequest.getUserRead());
createPeopleGroupIfNeeded(shareRequest.getUserWrite());
validateGroups(shareRequest.getGroupRead());
validateGroups(shareRequest.getGroupWrite());
String groupRead = getGroupString(shareRequest.getGroupRead(), shareRequest.getUserRead());
String groupWrite = getGroupString(shareRequest.getGroupWrite(), shareRequest.getUserWrite());
Node node = vospaceClient.getNode(shareRequest.getPath());
getNodeProperty(node, NodeProperties.GROUP_READ_URI).setValue(groupRead);
getNodeProperty(node, NodeProperties.GROUP_WRITE_URI).setValue(groupWrite);
vospaceClient.setNode(node);
}
private Property getNodeProperty(Node node, String uri) {
for (Property property : node.getProperties()) {
if (uri.equals(property.getUri())) {
return property;
}
}
Property property = new Property();
property.setUri(uri);
node.getProperties().add(property);
return property;
}
private void createPeopleGroupIfNeeded(List<String> users) {
for (String username : users) {
if (!existingPeopleGroups.contains(username)) {
String completeGroupName = "people." + username.replace(".", "\\.");
String rapId = getRapId(username);
gmsClient.createGroup(completeGroupName, true);
gmsClient.addMember(completeGroupName, rapId);
gmsClient.addPermission(completeGroupName, rapId, Permission.VIEW_MEMBERS);
existingPeopleGroups.add(username);
}
}
}
private String getRapId(String username) {
for (Map.Entry<String, String> entry : existingUsers.entrySet()) {
if (entry.getValue().equals(username)) {
return entry.getKey();
}
}
throw new BadRequestException("Unable to find an identifier for user " + username);
}
private void validateGroups(List<String> groups) {
for (String group : groups) {
if (!existingGroups.contains(group)) {
throw new BadRequestException("Group " + group + " doesn't exist");
}
}
}
private String getGroupString(List<String> groups, List<String> users) {
List<String> values = new ArrayList<>(groups);
for (String user : users) {
values.add("people." + user.replace(".", "\\."));
}
return String.join(" ", values);
}
private void loadInfoFromServices() {
loadAccessToken();
if (lastUpdate == null || new Date().getTime() - lastUpdate.getTime() > 3600 * 100) {
LOG.trace("Loading existing users and groups from services");
CompletableFuture.allOf(
CompletableFuture.runAsync(() -> loadGroups()),
CompletableFuture.runAsync(() -> loadUsers())
).join();
}
}
public void loadAccessToken() {
if (tokenContext == null) {
tokenContext = new TokenContext();
}
if (tokenContext.isTokenExpired()) {
tokenContext.setAccessTokenResponse(rapClient.getAccessTokenFromClientCredentials());
gmsClient.setAccessToken(tokenContext.getAccessToken());
}
}
private void loadGroups() {
List<String> groups = new ArrayList<>();
List<String> peopleGroups = new ArrayList<>();
for (String group : gmsClient.listGroups("", true)) {
if (group.startsWith("people.")) {
String singleUserGroup = group.substring("people.".length()).replace("\\.", ".");
peopleGroups.add(singleUserGroup);
} else {
groups.add(group);
}
}
existingGroups.addAll(groups);
existingPeopleGroups.addAll(peopleGroups);
}
private void loadUsers() {
for (RapUser user : rapClient.getUsers(trustedEppnScope, tokenContext)) {
for (Identity identity : user.getIdentities()) {
if (identity.getType() == IdentityType.EDU_GAIN
&& identity.getEppn().endsWith("@" + trustedEppnScope)) {
String username = identity.getEppn().substring(0, identity.getEppn().indexOf("@"));
existingUsers.put(user.getId(), username);
break;
}
}
}
}
}
...@@ -9,3 +9,5 @@ spring.profiles.active=dev ...@@ -9,3 +9,5 @@ spring.profiles.active=dev
cors.allowed.origin=http://localhost:8080 cors.allowed.origin=http://localhost:8080
logging.level.it.inaf=TRACE logging.level.it.inaf=TRACE
trusted.eppn.scope=inaf.it
package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.vospace.ui.data.SharingInfo;
import it.inaf.ia2.vospace.ui.service.SharingService;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
public class SharingControllerTest {
@MockBean
private SharingService service;
@Autowired
private MockMvc mockMvc;
@Test
public void testListNodesEmpty() throws Exception {
when(service.getSharingInfo()).thenReturn(new SharingInfo());
mockMvc.perform(get("/sharing"))
.andExpect(status().isOk());
verify(service, times(1)).getSharingInfo();
}
}
package it.inaf.ia2.vospace.ui.service;
import it.inaf.ia2.gms.client.GmsClient;
import it.inaf.ia2.rap.client.ClientCredentialsRapClient;
import it.inaf.ia2.rap.data.AccessTokenResponse;
import it.inaf.ia2.rap.data.Identity;
import it.inaf.ia2.rap.data.IdentityType;
import it.inaf.ia2.rap.data.RapUser;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.ShareRequest;
import it.inaf.ia2.vospace.ui.data.SharingInfo;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Property;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class SharingServiceTest {
@Mock
private GmsClient gmsClient;
@Mock
private ClientCredentialsRapClient rapClient;
@Mock
private HttpServletRequest servletRequest;
@Mock
private VOSpaceClient vospaceClient;
@InjectMocks
private SharingService sharingService;
@BeforeEach
public void setUp() {
sharingService.trustedEppnScope = "inaf.it";
when(rapClient.getAccessTokenFromClientCredentials()).thenReturn(new AccessTokenResponse());
when(rapClient.getUsers(anyString(), any())).thenReturn(getRapUsers());
when(gmsClient.listGroups(eq(""), eq(true))).thenReturn(
Arrays.asList("group1", "group2", "people.mario\\.rossi", "people.john\\.doe"));
}
@Test
public void testGetSharingInfo() {
SharingInfo sharingInfo = sharingService.getSharingInfo();
assertEquals(2, sharingInfo.getGroups().size());
assertTrue(sharingInfo.getGroups().contains("group1"));
assertTrue(sharingInfo.getGroups().contains("group2"));
assertEquals(4, sharingInfo.getPeople().size());
assertTrue(sharingInfo.getPeople().contains("mario.rossi"));
assertTrue(sharingInfo.getPeople().contains("bianca.verdi"));
assertTrue(sharingInfo.getPeople().contains("paolo.gialli"));
assertTrue(sharingInfo.getPeople().contains("john.doe"));
}
@Test
public void testSetNodeGroups() {
DataNode node = new DataNode();
node.setUri("vos://example.com!vospace/mynode");
Property groupReadProperty = new Property();
groupReadProperty.setUri(NodeProperties.GROUP_READ_URI);
groupReadProperty.setValue("group1");
node.getProperties().add(groupReadProperty);
when(vospaceClient.getNode(any())).thenReturn(node);
ShareRequest shareRequest = new ShareRequest();
shareRequest.setPath("/mynode");
shareRequest.setUserRead(Arrays.asList("bianca.verdi", "john.doe"));
shareRequest.setGroupRead(Arrays.asList("group1", "group2"));
shareRequest.setUserWrite(Arrays.asList("bianca.verdi"));
shareRequest.setGroupWrite(Arrays.asList("group2"));
sharingService.setNodeGroups(shareRequest);
verify(gmsClient, times(1)).createGroup(eq("people.bianca\\.verdi"), eq(true));
verify(vospaceClient, times(1)).setNode(argThat(n -> {
List<String> groupRead = NodeProperties.getNodePropertyAsListByURI(n, NodeProperties.GROUP_READ_URI);
assertEquals(4, groupRead.size());
assertTrue(groupRead.contains("people.bianca\\.verdi"));
assertTrue(groupRead.contains("people.john\\.doe"));
assertTrue(groupRead.contains("group1"));
assertTrue(groupRead.contains("group2"));
List<String> groupWrite = NodeProperties.getNodePropertyAsListByURI(n, NodeProperties.GROUP_WRITE_URI);
assertEquals(2, groupWrite.size());
assertTrue(groupWrite.contains("people.bianca\\.verdi"));
assertTrue(groupWrite.contains("group2"));
return true;
}));
}
private List<RapUser> getRapUsers() {
List<RapUser> users = new ArrayList<>();
users.add(getRapUser("1", "mario.rossi"));
users.add(getRapUser("2", "bianca.verdi"));
users.add(getRapUser("3", "paolo.gialli"));
RapUser ignored = getRapUser("4", "name.surname");
ignored.getIdentities().get(0).setType(IdentityType.GOOGLE);
users.add(ignored);
return users;
}
private RapUser getRapUser(String id, String name) {
RapUser user = new RapUser();
user.setId(id);
List<Identity> identities = new ArrayList<>();
Identity identity = new Identity();
identity.setType(IdentityType.EDU_GAIN);
identity.setEppn(name + "@inaf.it");
identities.add(identity);
user.setIdentities(identities);
return user;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment