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 4f632772d2a040ef538775e086e0272136c574b8..7a56d7b84a457279c146eada6ebf876af00a2926 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 @@ -14,12 +14,14 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; 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.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -64,6 +66,62 @@ public class JWTWebServiceController { } } + /** + * This endpoint is compliant with the IVOA GMS standard. Warning: for + * supporting the groups of groups (with dots inside) the path variable must + * be defined adding ".+", otherwise Spring will think it is a file + * extension (thanks https://stackoverflow.com/a/16333149/771431) + */ + @GetMapping(value = "/search/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE) + public void isMemberOf(@PathVariable("group") String group, Principal principal, HttpServletResponse response) throws IOException { + + String userId = principal.getName(); + + List<String> groupNames = extractGroupNames(group); + + boolean isMemeber = false; + String parentPath = ""; // starting from ROOT + for (String groupName : groupNames) { + Optional<GroupEntity> optionalGroup = groupsDAO.findGroupByParentAndName(parentPath, groupName); + if (optionalGroup.isPresent()) { + GroupEntity groupEntity = optionalGroup.get(); + parentPath = groupEntity.getPath(); + isMemeber = membershipsDAO.isMemberOf(userId, groupEntity.getId()); + if (isMemeber) { + break; + } + } else { + break; + } + } + + if (isMemeber) { + try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + pw.println(group); + } + } + // else: empty response (as defined by GMS standard) + } + + private List<String> extractGroupNames(String groupStr) { + + List<String> names = new ArrayList<>(); + String currentName = ""; + for (int i = 0; i < groupStr.length(); i++) { + char c = groupStr.charAt(i); + // dot is the group separator and it must be escaped if used inside + // group names + if (c == '.' && groupStr.charAt(i - 1) != '\\') { + names.add(currentName.replace("\\.", ".")); + currentName = ""; + } else { + currentName += c; + } + } + names.add(currentName); + return names; + } + private Set<String> getAllIdentifiers(List<GroupEntity> groups) { Set<String> allIdentifiers = new HashSet<>(); diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java index 5c05f1015cfc391349af49eeb372cdb894612eaa..ae23d57b9127831a17322cc876f22f2f929d24f3 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/LoggingDAO.java @@ -53,16 +53,20 @@ public class LoggingDAO { } public void logAction(String action) { - String sql = "INSERT INTO audit_log (\"user\", action, ip_address) VALUES (?, ?, ?)"; + try { + String sql = "INSERT INTO audit_log (\"user\", action, ip_address) VALUES (?, ?, ?)"; - jdbcTemplate.update(conn -> { - PreparedStatement ps = conn.prepareStatement(sql); - int i = 0; - ps.setString(++i, getUser()); - ps.setString(++i, action); - ps.setString(++i, getIPAddress()); - return ps; - }); + jdbcTemplate.update(conn -> { + PreparedStatement ps = conn.prepareStatement(sql); + int i = 0; + ps.setString(++i, getUser()); + ps.setString(++i, action); + ps.setString(++i, getIPAddress()); + return ps; + }); + } catch (Throwable t) { + LOG.error("Exception while trying to save audit log", t); + } } private String getIPAddress() { diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..918cbe3979f96f47408e5ff1eb74759a84c9757a --- /dev/null +++ b/gms/src/test/java/it/inaf/ia2/gms/controller/JWTWebServiceControllerTest.java @@ -0,0 +1,82 @@ +package it.inaf.ia2.gms.controller; + +import it.inaf.ia2.gms.persistence.GroupsDAO; +import it.inaf.ia2.gms.persistence.MembershipsDAO; +import it.inaf.ia2.gms.persistence.model.GroupEntity; +import java.security.Principal; +import java.util.Optional; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.Mockito.when; +import org.mockito.junit.MockitoJUnitRunner; +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.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@RunWith(MockitoJUnitRunner.class) +public class JWTWebServiceControllerTest { + + @Mock + private MembershipsDAO membershipsDAO; + + @Mock + private GroupsDAO groupsDAO; + + @InjectMocks + private JWTWebServiceController controller; + + private final Principal principal = new Principal() { + @Override + public String getName() { + return "TEST_PRINCIPAL"; + } + }; + + private MockMvc mockMvc; + + @Before + public void init() { + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + @Test + public void testIsMemberOf() throws Exception { + + GroupEntity group1 = new GroupEntity(); + group1.setId("1"); + group1.setPath("1"); + group1.setName("group.1"); + + GroupEntity group2 = new GroupEntity(); + group2.setId("2"); + group2.setPath("1.2"); + group2.setName("subgroup"); + + GroupEntity group3 = new GroupEntity(); + group3.setId("3"); + group3.setPath("1.2.3"); + group3.setName("subsubgroup"); + + when(groupsDAO.findGroupByParentAndName(eq(""), eq("group.1"))) + .thenReturn(Optional.of(group1)); + when(groupsDAO.findGroupByParentAndName(eq("1"), eq("subgroup"))) + .thenReturn(Optional.of(group2)); + when(groupsDAO.findGroupByParentAndName(eq("1.2"), eq("subsubgroup"))) + .thenReturn(Optional.of(group3)); + + when(membershipsDAO.isMemberOf(anyString(), eq(group3.getId()))).thenReturn(true); + + String group = "group\\.1.subgroup.subsubgroup"; + + mockMvc.perform(get("/ws/jwt/search/" + group).principal(principal)) + .andExpect(status().isOk()) + .andExpect(content().string(group + "\n")); + } +}