diff --git a/gms/pom.xml b/gms/pom.xml index c54b41834f454586d6e13ff304687ec9fddd5b8c..a141c026be049fc255e2118f016f172f030c0da1 100644 --- a/gms/pom.xml +++ b/gms/pom.xml @@ -165,7 +165,7 @@ <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> - <version>0.8.4</version> + <version>0.8.6</version> <executions> <execution> <goals> diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/HomePageController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/HomePageController.java index ab10140335c47ad563544328b6260f4b077ad8da..d39135f85d0b59659946b9e2fefcbcb4d4845689 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/HomePageController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/HomePageController.java @@ -82,9 +82,9 @@ public class HomePageController { @GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE) public String index(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - Optional<InvitedRegistration> optReg = invitedRegistrationManager.completeInvitedRegistrationIfNecessary(); + Optional<List<InvitedRegistration>> optReg = invitedRegistrationManager.completeInvitedRegistrationIfNecessary(); if (optReg.isPresent()) { - request.setAttribute("invited-registration", optReg.get()); + request.setAttribute("invited-registrations", optReg.get()); return "/registration-completed"; } diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/InvitedRegistrationController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/InvitedRegistrationController.java index 82dd95a212ba2aa5211cea7e6d613a036bac64da..e5c2a18472ab425d1726386a7a579366958973b5 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/controller/InvitedRegistrationController.java +++ b/gms/src/main/java/it/inaf/ia2/gms/controller/InvitedRegistrationController.java @@ -5,6 +5,8 @@ import it.inaf.ia2.gms.persistence.model.InvitedRegistration; import it.inaf.ia2.gms.service.GroupNameService; import java.io.IOException; import java.io.InputStream; +import java.util.Collections; +import java.util.List; import java.util.Scanner; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -32,7 +34,7 @@ public class InvitedRegistrationController { String html = getFileContent("invited-registration.html") .replace("#EMAIL#", invitedRegistration.getEmail()) - .replace("#GROUPS#", getGroupsList(invitedRegistration)) + .replace("#GROUPS#", getGroupsList(Collections.singletonList(invitedRegistration))) .replace("#HOME#", request.getContextPath()); response.getOutputStream().print(html); @@ -43,14 +45,14 @@ public class InvitedRegistrationController { response.setContentType("text/html;charset=UTF-8"); - InvitedRegistration invitedRegistration = (InvitedRegistration) request.getAttribute("invited-registration"); - if (invitedRegistration == null) { + List<InvitedRegistration> invitedRegistrations = (List<InvitedRegistration>) request.getAttribute("invited-registrations"); + if (invitedRegistrations == null) { // redirect to home String baseUrl = ServletUriComponentsBuilder.fromCurrentContextPath().build().toUriString(); response.sendRedirect(baseUrl); } else { String html = getFileContent("registration-completed.html") - .replace("#GROUPS#", getGroupsList(invitedRegistration)) + .replace("#GROUPS#", getGroupsList(invitedRegistrations)) .replace("#HOME#", request.getContextPath()); response.getOutputStream().print(html); @@ -64,17 +66,19 @@ public class InvitedRegistrationController { } private String getFileContent(String templateFileName) throws IOException { - try (InputStream in = InvitedRegistrationController.class.getClassLoader().getResourceAsStream("templates/" + templateFileName)) { + try ( InputStream in = InvitedRegistrationController.class.getClassLoader().getResourceAsStream("templates/" + templateFileName)) { Scanner s = new Scanner(in).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; } } - private String getGroupsList(InvitedRegistration invitedRegistration) { + private String getGroupsList(List<InvitedRegistration> invitedRegistrations) { String groups = "<ul>"; - for (String groupName : groupNameService.getGroupsNamesFromIdentifiers( - invitedRegistration.getGroupsPermissions().keySet())) { - groups += "<li>" + groupName + "</li>"; + for (InvitedRegistration invitedRegistration : invitedRegistrations) { + for (String groupName : groupNameService.getGroupsNamesFromIdentifiers( + invitedRegistration.getGroupsPermissions().keySet())) { + groups += "<li>" + groupName + "</li>"; + } } groups += "</ul>"; return groups; 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 64f838d69b041db31b4fff84d054e0b762415255..8607c4b2e1ca838e1dc166e2fb634b8d80135e2b 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 @@ -12,6 +12,7 @@ import it.inaf.ia2.gms.model.response.UserPermission; import it.inaf.ia2.gms.persistence.GroupsDAO; import it.inaf.ia2.gms.persistence.PermissionsDAO; import it.inaf.ia2.gms.persistence.model.GroupEntity; +import it.inaf.ia2.gms.persistence.model.InvitedRegistration; import it.inaf.ia2.gms.persistence.model.PermissionEntity; import it.inaf.ia2.gms.service.GroupNameService; import it.inaf.ia2.gms.service.GroupsService; @@ -89,7 +90,7 @@ public class JWTWebServiceController { List<String> names = groupNameService.getGroupsNames(memberships); - try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { for (String name : names) { pw.println(name); @@ -127,7 +128,7 @@ public class JWTWebServiceController { } if (isMember) { - try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { pw.println(group); } } @@ -154,7 +155,7 @@ public class JWTWebServiceController { }); } - try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { for (String groupName : groupNameService.getGroupsNames(visibleSubgroups)) { pw.println(getShortGroupName(groupName, group)); } @@ -185,7 +186,7 @@ public class JWTWebServiceController { } response.setStatus(HttpServletResponse.SC_CREATED); - try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { pw.println(groupParam); } } @@ -204,7 +205,7 @@ public class JWTWebServiceController { List<GroupEntity> groups = membershipManager.getUserGroups(parent, userId); - try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { for (String groupName : groupNameService.getGroupsNames(groups)) { pw.println(getShortGroupName(groupName, group)); } @@ -239,7 +240,7 @@ public class JWTWebServiceController { public void getUserPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") Optional<String> userId, HttpServletRequest request, HttpServletResponse response) throws IOException { if (userId.isPresent()) { - try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { for (UserPermission userPermission : searchService.getUserPermission(userId.get(), permissionsManager.getCurrentUserPermissions(getRoot()))) { String group = String.join(".", userPermission.getGroupCompleteName()); pw.println(group + " " + userPermission.getPermission()); @@ -247,7 +248,7 @@ public class JWTWebServiceController { } } else { GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames)); - try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { for (it.inaf.ia2.gms.model.UserPermission up : permissionsManager.getAllPermissions(groupEntity)) { pw.println(up.getUser().getId() + " " + up.getPermission()); } @@ -277,6 +278,28 @@ public class JWTWebServiceController { response.setStatus(HttpServletResponse.SC_NO_CONTENT); } + @GetMapping(value = "/check-invited-registration", produces = MediaType.TEXT_PLAIN_VALUE) + public void completeInvitedRegistrationIfNecessary(Principal principal, HttpServletResponse response) throws IOException { + + String userId = principal.getName(); + + Set<String> groupIds = new HashSet<>(); + for (InvitedRegistration invitedRegistration : invitedRegistrationManager.completeInvitedRegistrationIfNecessary(userId)) { + groupIds.addAll(invitedRegistration.getGroupsPermissions().keySet()); + } + List<GroupEntity> groups = groupsDAO.findGroupsByIds(groupIds); + + if (!groups.isEmpty()) { + List<String> names = groupNameService.getGroupsNames(groups); + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { + + for (String name : names) { + pw.println(name); + } + } + } + } + @PostMapping(value = "/invited-registration", produces = MediaType.TEXT_PLAIN_VALUE) public void addInvitedRegistration(@RequestParam("token_hash") String tokenHash, @RequestParam("email") String email, @RequestParam("groups") String groupNamesAndPermissionsParam, HttpServletResponse response) { @@ -314,7 +337,7 @@ public class JWTWebServiceController { } } - try (PrintWriter pw = new PrintWriter(response.getOutputStream())) { + try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) { for (RapUser member : membershipManager.getMembers(groupEntity)) { if (selectedUserIds == null || selectedUserIds.contains(member.getId())) { pw.println(member.getPrimaryEmail()); diff --git a/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java b/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java index f30dca3c123a25dc9c7afec26bbe5669cfa81e5f..dec9449f56626d78f3fdfd68f8a074f4d17a5192 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java +++ b/gms/src/main/java/it/inaf/ia2/gms/manager/InvitedRegistrationManager.java @@ -1,5 +1,6 @@ package it.inaf.ia2.gms.manager; +import it.inaf.ia2.gms.authn.SessionData; import it.inaf.ia2.gms.exception.BadRequestException; import it.inaf.ia2.gms.exception.NotFoundException; import it.inaf.ia2.gms.exception.UnauthorizedException; @@ -12,6 +13,7 @@ import it.inaf.ia2.gms.persistence.MembershipsDAO; import it.inaf.ia2.gms.persistence.model.GroupEntity; import it.inaf.ia2.gms.persistence.model.InvitedRegistration; import it.inaf.ia2.gms.persistence.model.MembershipEntity; +import it.inaf.ia2.gms.rap.RapClient; import it.inaf.ia2.gms.service.PermissionsService; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -23,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -48,9 +51,15 @@ public class InvitedRegistrationManager extends UserAwareComponent { private InvitedRegistrationDAO invitedRegistrationDAO; @Autowired - private LoggingDAO loggingDAO; + private RapClient rapClient; + + @Autowired + private SessionData sessionData; @Autowired + private LoggingDAO loggingDAO; + + @Autowired(required = false) private HttpSession httpSession; public void addInvitedRegistration(String tokenHash, String email, Map<GroupEntity, Permission> groupsPermissions) { @@ -93,35 +102,66 @@ public class InvitedRegistrationManager extends UserAwareComponent { } } - public Optional<InvitedRegistration> completeInvitedRegistrationIfNecessary() { + public Optional<List<InvitedRegistration>> completeInvitedRegistrationIfNecessary() { - InvitedRegistration invitedRegistration = (InvitedRegistration) httpSession.getAttribute(INVITED_REGISTRATION); + List<InvitedRegistration> invitedRegistrations = completeInvitedRegistrationIfNecessary(sessionData.getUserId()); - if (invitedRegistration != null) { + InvitedRegistration invitedRegistrationFromToken = (InvitedRegistration) httpSession.getAttribute(INVITED_REGISTRATION); - for (Map.Entry<String, Permission> entry : invitedRegistration.getGroupsPermissions().entrySet()) { - String groupId = entry.getKey(); - String userId = getCurrentUserId(); + if (invitedRegistrationFromToken != null) { - GroupEntity groupEntity = groupsDAO.findGroupById(groupId).get(); + boolean alreadyDone = invitedRegistrations.stream().map(reg -> reg.getId()) + .anyMatch(id -> id.equals(invitedRegistrationFromToken.getId())); - MembershipEntity membershipEntity = new MembershipEntity(); - membershipEntity.setUserId(userId); - membershipEntity.setGroupId(groupId); - membershipsDAO.addMember(membershipEntity); - - permissionsService.addPermission(groupEntity, userId, entry.getValue()); + if (!alreadyDone) { + completeInvitedRegistration(invitedRegistrationFromToken); + invitedRegistrations.add(invitedRegistrationFromToken); } + httpSession.removeAttribute(INVITED_REGISTRATION); + } - invitedRegistration.setUserId(getCurrentUserId()); - // FIXME (workaround): separated update for user and done in order to use triggers - invitedRegistrationDAO.setRegistrationUser(invitedRegistration); - invitedRegistrationDAO.setRegistrationDone(invitedRegistration); + return Optional.ofNullable(invitedRegistrations.isEmpty() ? null : invitedRegistrations); + } - httpSession.removeAttribute(INVITED_REGISTRATION); + /** + * This method can be called from web service, since it doesn't rely on + * session. + */ + public List<InvitedRegistration> completeInvitedRegistrationIfNecessary(String userId) { + + List<InvitedRegistration> invitedRegistrations = new ArrayList<>(); + + List<String> emailAddresses = rapClient.getUser(userId).getIdentities().stream() + .map(identity -> identity.getEmail()) + .collect(Collectors.toList()); + + for (InvitedRegistration invitedRegistration : invitedRegistrationDAO.getInvitedRegistrationsFromEmailAddresses(emailAddresses)) { + completeInvitedRegistration(invitedRegistration); + invitedRegistrations.add(invitedRegistration); + } + + return invitedRegistrations; + } + + private void completeInvitedRegistration(InvitedRegistration invitedRegistration) { + for (Map.Entry<String, Permission> entry : invitedRegistration.getGroupsPermissions().entrySet()) { + String groupId = entry.getKey(); + String userId = getCurrentUserId(); + + GroupEntity groupEntity = groupsDAO.findGroupById(groupId).get(); + + MembershipEntity membershipEntity = new MembershipEntity(); + membershipEntity.setUserId(userId); + membershipEntity.setGroupId(groupId); + membershipsDAO.addMember(membershipEntity); + + permissionsService.addPermission(groupEntity, userId, entry.getValue()); } - return Optional.ofNullable(invitedRegistration); + invitedRegistration.setUserId(getCurrentUserId()); + // FIXME (workaround): separated update for user and done in order to use triggers + invitedRegistrationDAO.setRegistrationUser(invitedRegistration); + invitedRegistrationDAO.setRegistrationDone(invitedRegistration); } public List<InvitedRegistrationItem> getInvitedRegistrationsForGroup(GroupEntity group) { diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAO.java index 43415d614786612d41c30029d7253ea1d76038a6..89698eb2b09fe8d65bdbc55c9c772f3a9334bbbb 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAO.java +++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAO.java @@ -3,6 +3,8 @@ package it.inaf.ia2.gms.persistence; import it.inaf.ia2.gms.model.Permission; import it.inaf.ia2.gms.persistence.model.InvitedRegistration; import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Collections; @@ -150,7 +152,7 @@ public class InvitedRegistrationDAO { public List<InvitedRegistration> getPendingInvitedRegistrationsForGroup(String groupId) { - String sql = "SELECT id, email, creation_time, permission\n" + String sql = "SELECT id, email, creation_time, group_id, permission\n" + "FROM invited_registration_request r\n" + "JOIN invited_registration_request_group rg ON r.id = rg.request_id\n" + "WHERE done IS NOT TRUE AND rg.group_id = ?"; @@ -160,39 +162,67 @@ public class InvitedRegistrationDAO { ps.setString(1, groupId); }, rs -> { - // key: id - Map<String, InvitedRegistration> map = new HashMap<>(); + List<InvitedRegistration> registrations = getInvitedRegistrationsFromResultSet(rs); + Collections.sort(registrations, (reg1, reg2) -> reg1.getEmail().compareToIgnoreCase(reg2.getEmail())); + return registrations; + }); + } + + public List<InvitedRegistration> getInvitedRegistrationsFromEmailAddresses(List<String> addresses) { + + if (addresses.isEmpty()) { + throw new IllegalArgumentException("List of email addresses is empty"); + } + + String sql = "SELECT id, email, creation_time, group_id, permission\n" + + "FROM invited_registration_request r\n" + + "JOIN invited_registration_request_group rg ON r.id = rg.request_id\n" + + "WHERE done IS NOT TRUE AND (" + + String.join(" OR ", Collections.nCopies(addresses.size(), "email ILIKE ?")) + + ")"; + + return jdbcTemplate.query(sql, + ps -> { + for (int i = 0; i < addresses.size(); i++) { + ps.setString(i + 1, addresses.get(i)); + } + }, + rs -> { + return getInvitedRegistrationsFromResultSet(rs); + }); + } - while (rs.next()) { + private List<InvitedRegistration> getInvitedRegistrationsFromResultSet(ResultSet rs) throws SQLException { - String id = rs.getString("id"); + // key: invited registration id + Map<String, InvitedRegistration> map = new HashMap<>(); - InvitedRegistration reg = map.get(id); - if (reg == null) { - String email = rs.getString("email"); - Date creationTime = new Date(rs.getDate("creation_time").getTime()); - reg = new InvitedRegistration() - .setId(id) - .setEmail(email) - .setCreationTime(creationTime); - map.put(id, reg); - } + while (rs.next()) { - if (reg.getGroupsPermissions() == null) { - reg.setGroupsPermissions(new HashMap<>()); - } + String id = rs.getString("id"); - Permission permission = Permission.valueOf(rs.getString("permission")); + InvitedRegistration reg = map.get(id); + if (reg == null) { + String email = rs.getString("email"); + Date creationTime = new Date(rs.getDate("creation_time").getTime()); + reg = new InvitedRegistration() + .setId(id) + .setEmail(email) + .setCreationTime(creationTime); + map.put(id, reg); + } - reg.getGroupsPermissions().put(groupId, permission); - } + if (reg.getGroupsPermissions() == null) { + reg.setGroupsPermissions(new HashMap<>()); + } - List<InvitedRegistration> registrations = new ArrayList<>(map.values()); + Permission permission = Permission.valueOf(rs.getString("permission")); - Collections.sort(registrations, (reg1, reg2) -> reg1.getEmail().compareToIgnoreCase(reg2.getEmail())); + String groupId = rs.getString("group_id"); + reg.getGroupsPermissions().put(groupId, permission); + } - return registrations; - }); + return new ArrayList<>(map.values()); } public void deleteInvitedRegistrationRequest(String requestId, String groupId) { 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 b04b810e79d9ff06dd633ee3244e76afaf07ecb5..cf7cf8d53fb68ad3d662d3cd51f1331528a53676 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 @@ -9,6 +9,7 @@ import javax.servlet.http.HttpServletRequest; import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; @@ -51,7 +52,7 @@ public class LoggingDAO { private String getStackTraceString(Exception ex) { StringWriter sw = new StringWriter(); - try (PrintWriter pw = new PrintWriter(sw)) { + try ( PrintWriter pw = new PrintWriter(sw)) { ex.printStackTrace(pw); } return sw.toString(); @@ -91,9 +92,11 @@ public class LoggingDAO { if (request.getUserPrincipal() != null && request.getUserPrincipal() instanceof RapPrincipal) { return request.getUserPrincipal().getName(); } else if (request.getSession(false) != null) { - return sessionData.getUserId(); - } else { - return null; + try { + return sessionData.getUserId(); + } catch (BeanCreationException ex) { + } } + return null; } } diff --git a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java index 1b3d79268d8f85acf19a410169fa31170b2440c5..8611fc6c9dc854501b8e6038dc74b24247557e53 100644 --- a/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java +++ b/gms/src/main/java/it/inaf/ia2/gms/rap/RapClient.java @@ -1,5 +1,7 @@ package it.inaf.ia2.gms.rap; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import it.inaf.ia2.gms.authn.SessionData; import it.inaf.ia2.gms.model.RapUser; import java.util.ArrayList; @@ -25,6 +27,8 @@ import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; @Component @@ -62,6 +66,8 @@ public class RapClient { private final RestTemplate refreshTokenRestTemplate; + private final ObjectMapper objectMapper = new ObjectMapper(); + @Autowired public RapClient(RestTemplate rapRestTemplate) { this.rapRestTemplate = rapRestTemplate; @@ -112,14 +118,30 @@ public class RapClient { private <R, T> R httpCall(Function<HttpEntity<?>, R> function, T body) { try { - return function.apply(getEntity(body)); - } catch (HttpClientErrorException.Unauthorized ex) { - if (request.getSession(false) == null) { - // we can't refresh the token without a session - throw ex; + try { + return function.apply(getEntity(body)); + } catch (HttpClientErrorException.Unauthorized ex) { + if (request.getSession(false) == null || sessionData.getExpiresIn() > 0) { + // we can't refresh the token without a session + throw ex; + } + refreshToken(); + return function.apply(getEntity(body)); + } + } catch (HttpStatusCodeException ex) { + try { + Map<String, String> map = objectMapper.readValue(ex.getResponseBodyAsString(), Map.class); + if (map.containsKey("error")) { + String error = map.get("error"); + if (ex instanceof HttpClientErrorException) { + throw new HttpClientErrorException(ex.getStatusCode(), error); + } else if (ex instanceof HttpServerErrorException) { + throw new HttpServerErrorException(ex.getStatusCode(), error); + } + } + } catch (JsonProcessingException ignore) { } - refreshToken(); - return function.apply(getEntity(body)); + throw ex; } } diff --git a/gms/src/main/resources/application.properties b/gms/src/main/resources/application.properties index c0b7369f43be795450a44cf3ed950037b589cf52..204831fa09d42fd35d8b574adade4542c92d6ba7 100644 --- a/gms/src/main/resources/application.properties +++ b/gms/src/main/resources/application.properties @@ -17,7 +17,7 @@ logging.level.org.springframework.security=DEBUG logging.level.org.springframework.jdbc=TRACE logging.level.org.springframework.web=TRACE -spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/postgres +spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=gms spring.datasource.password=gms diff --git a/gms/src/test/java/it/inaf/ia2/gms/manager/InvitedRegistrationManagerTest.java b/gms/src/test/java/it/inaf/ia2/gms/manager/InvitedRegistrationManagerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..13cf87d37e771480d2d06898e3cbcf7e356f08fa --- /dev/null +++ b/gms/src/test/java/it/inaf/ia2/gms/manager/InvitedRegistrationManagerTest.java @@ -0,0 +1,168 @@ +package it.inaf.ia2.gms.manager; + +import it.inaf.ia2.gms.authn.SessionData; +import it.inaf.ia2.gms.model.Identity; +import it.inaf.ia2.gms.model.IdentityType; +import it.inaf.ia2.gms.model.Permission; +import it.inaf.ia2.gms.model.RapUser; +import it.inaf.ia2.gms.persistence.GroupsDAO; +import it.inaf.ia2.gms.persistence.InvitedRegistrationDAO; +import it.inaf.ia2.gms.persistence.LoggingDAO; +import it.inaf.ia2.gms.persistence.MembershipsDAO; +import it.inaf.ia2.gms.persistence.model.GroupEntity; +import it.inaf.ia2.gms.persistence.model.InvitedRegistration; +import it.inaf.ia2.gms.rap.RapClient; +import it.inaf.ia2.gms.service.PermissionsService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.servlet.http.HttpSession; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.any; +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.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class InvitedRegistrationManagerTest { + + private static final String USER_ID = "USER_ID"; + private static final String EMAIL = "user@inaf.it"; + + @Mock + private GroupsDAO groupsDAO; + @Mock + private MembershipsDAO membershipsDAO; + @Mock + private PermissionsService permissionsService; + @Mock + private PermissionsManager permissionsManager; + @Mock + private InvitedRegistrationDAO invitedRegistrationDAO; + @Mock + private RapClient rapClient; + @Mock + private SessionData sessionData; + @Mock + private LoggingDAO loggingDAO; + @Mock + private HttpSession httpSession; + + @InjectMocks + private InvitedRegistrationManager invitedRegistrationManager; + + @Before + public void setUp() { + UserAwareComponentTestUtil.setUser(invitedRegistrationManager, USER_ID); + } + + @Test + public void testAddInvitedRegistration() { + + Map<GroupEntity, Permission> groupsPermissions = new HashMap<>(); + GroupEntity groupEntity = new GroupEntity(); + groupEntity.setId("b"); + groupEntity.setPath("a.b"); + groupEntity.setName("B"); + groupsPermissions.put(groupEntity, Permission.MANAGE_MEMBERS); + + when(permissionsManager.getCurrentUserPermission(any())).thenReturn(Permission.ADMIN); + + invitedRegistrationManager.addInvitedRegistration("token_hash", EMAIL, groupsPermissions); + + verify(invitedRegistrationDAO, times(1)).addInvitedRegistration(any()); + } + + @Test + public void testCompleteInvitedRegistrationIfNecessaryGetByEmail() { + + Map<String, Permission> map1 = new HashMap<>(); + map1.put("g1", Permission.MANAGE_MEMBERS); + + InvitedRegistration regFromToken = new InvitedRegistration() + .setId("reg1") + .setEmail(EMAIL) + .setGroupsPermissions(map1); + + when(httpSession.getAttribute(eq("invited-registration"))).thenReturn(regFromToken); + + when(sessionData.getUserId()).thenReturn(USER_ID); + + RapUser user = new RapUser(); + user.setId(USER_ID); + Identity identity = new Identity(); + identity.setType(IdentityType.EDU_GAIN); + identity.setEmail(EMAIL); + user.setIdentities(Collections.singletonList(identity)); + + when(rapClient.getUser(eq(USER_ID))).thenReturn(user); + + Map<String, Permission> map2 = new HashMap<>(); + map2.put("g2", Permission.MANAGE_MEMBERS); + + InvitedRegistration reg2 = new InvitedRegistration() + .setId("reg2") + .setEmail(EMAIL) + .setGroupsPermissions(map2); + + List<InvitedRegistration> registrations = new ArrayList<>(); + registrations.add(regFromToken); + registrations.add(reg2); + when(invitedRegistrationDAO.getInvitedRegistrationsFromEmailAddresses(any())).thenReturn(registrations); + + GroupEntity g1 = new GroupEntity(); + g1.setId("g1"); + GroupEntity g2 = new GroupEntity(); + g2.setId("g2"); + when(groupsDAO.findGroupById(eq("g1"))).thenReturn(Optional.of(g1)); + when(groupsDAO.findGroupById(eq("g2"))).thenReturn(Optional.of(g2)); + + invitedRegistrationManager.completeInvitedRegistrationIfNecessary(); + + verify(membershipsDAO, times(2)).addMember(any()); + verify(permissionsService, times(2)).addPermission(any(), any(), any()); + } + + @Test + public void testCompleteInvitedRegistrationIfNecessaryGetByToken() { + + Map<String, Permission> map1 = new HashMap<>(); + map1.put("g1", Permission.MANAGE_MEMBERS); + + InvitedRegistration regFromToken = new InvitedRegistration() + .setId("reg1") + .setEmail(EMAIL) + .setGroupsPermissions(map1); + + when(httpSession.getAttribute(eq("invited-registration"))).thenReturn(regFromToken); + + when(sessionData.getUserId()).thenReturn(USER_ID); + + RapUser user = new RapUser(); + user.setId(USER_ID); + Identity identity = new Identity(); + identity.setType(IdentityType.EDU_GAIN); + identity.setEmail("different-email"); + user.setIdentities(Collections.singletonList(identity)); + + when(rapClient.getUser(eq(USER_ID))).thenReturn(user); + + GroupEntity g1 = new GroupEntity(); + g1.setId("g1"); + when(groupsDAO.findGroupById(eq("g1"))).thenReturn(Optional.of(g1)); + + invitedRegistrationManager.completeInvitedRegistrationIfNecessary(); + + verify(membershipsDAO, times(1)).addMember(any()); + verify(permissionsService, times(1)).addPermission(any(), any(), any()); + } +} diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAOTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAOTest.java index 555f27b6b5d992148d60e315b2191955e0941460..f13c03dcbbc2ed52066f2129fc543777e7a7b205 100644 --- a/gms/src/test/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAOTest.java +++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/InvitedRegistrationDAOTest.java @@ -80,6 +80,11 @@ public class InvitedRegistrationDAOTest { assertEquals(reg.getEmail(), regFromGroup.getEmail()); assertNotNull(regFromGroup.getCreationTime()); + List<String> addresses = new ArrayList<>(); + addresses.add("test@inaf.it"); + addresses.add("test2@inaf.it"); + assertEquals(1, dao.getInvitedRegistrationsFromEmailAddresses(addresses).size()); + dao.setRegistrationDone(regFromGroup); assertTrue(dao.getPendingInvitedRegistrationsForGroup("group1").isEmpty()); diff --git a/gms/src/test/java/it/inaf/ia2/gms/rap/RapClientTest.java b/gms/src/test/java/it/inaf/ia2/gms/rap/RapClientTest.java new file mode 100644 index 0000000000000000000000000000000000000000..56651a9b695c4776aa94aa272a26dceb14690807 --- /dev/null +++ b/gms/src/test/java/it/inaf/ia2/gms/rap/RapClientTest.java @@ -0,0 +1,153 @@ +package it.inaf.ia2.gms.rap; + +import it.inaf.ia2.gms.authn.SessionData; +import it.inaf.ia2.gms.model.RapUser; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import org.mockito.Mock; +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.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpClientErrorException.Unauthorized; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.HttpServerErrorException.InternalServerError; +import org.springframework.web.client.RestTemplate; + +@RunWith(MockitoJUnitRunner.class) +public class RapClientTest { + + @Mock + private HttpServletRequest request; + + @Mock + private SessionData sessionData; + + @Mock + private RestTemplate restTemplate; + + @Mock + private RestTemplate refreshTokenRestTemplate; + + private RapClient rapClient; + + @Before + public void init() { + rapClient = new RapClient(restTemplate); + ReflectionTestUtils.setField(rapClient, "request", request); + ReflectionTestUtils.setField(rapClient, "refreshTokenRestTemplate", refreshTokenRestTemplate); + ReflectionTestUtils.setField(rapClient, "scope", "openid"); + } + + @Test + public void testUnauthorizedNoRefreshJsonMsg() { + + String jsonError = "{\"error\":\"Unauthorized: foo\"}"; + + HttpClientErrorException exception = Unauthorized + .create(HttpStatus.UNAUTHORIZED, "401", HttpHeaders.EMPTY, jsonError.getBytes(), StandardCharsets.UTF_8); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(new ParameterizedTypeReference<RapUser>() { + }))).thenThrow(exception); + + try { + rapClient.getUser("123"); + } catch (HttpClientErrorException ex) { + assertEquals("401 Unauthorized: foo", ex.getMessage()); + } + } + + @Test + public void testUnauthorizedNoRefreshNotJsonMsg() { + + String errorMessage = "THIS IS NOT A JSON"; + + HttpClientErrorException exception = Unauthorized + .create(HttpStatus.UNAUTHORIZED, "401", HttpHeaders.EMPTY, errorMessage.getBytes(), StandardCharsets.UTF_8); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(new ParameterizedTypeReference<RapUser>() { + }))).thenThrow(exception); + + try { + rapClient.getUser("123"); + } catch (HttpClientErrorException ex) { + assertNotNull(ex.getMessage()); + } + } + + @Test + public void testServerErrorJsonMsg() { + + String jsonError = "{\"error\":\"Fatal error\"}"; + + HttpServerErrorException exception = InternalServerError + .create(HttpStatus.INTERNAL_SERVER_ERROR, "500", HttpHeaders.EMPTY, jsonError.getBytes(), StandardCharsets.UTF_8); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(new ParameterizedTypeReference<RapUser>() { + }))).thenThrow(exception); + + try { + rapClient.getUser("123"); + } catch (HttpServerErrorException ex) { + assertEquals("500 Fatal error", ex.getMessage()); + } + } + + @Test + public void testRefreshToken() { + + when(request.getSession(eq(false))).thenReturn(mock(HttpSession.class)); + when(sessionData.getExpiresIn()).thenReturn(-100l); + + ReflectionTestUtils.setField(rapClient, "sessionData", sessionData); + ReflectionTestUtils.setField(rapClient, "clientId", "clientId"); + ReflectionTestUtils.setField(rapClient, "clientSecret", "clientSecret"); + ReflectionTestUtils.setField(rapClient, "accessTokenUri", "https://sso.ia2.inaf.it"); + + String jsonError = "{\"error\":\"Unauthorized: token expired\"}"; + + HttpClientErrorException exception = Unauthorized + .create(HttpStatus.UNAUTHORIZED, "401", HttpHeaders.EMPTY, jsonError.getBytes(), StandardCharsets.UTF_8); + + when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), any(HttpEntity.class), eq(new ParameterizedTypeReference<RapUser>() { + }))).thenThrow(exception) + .thenReturn(ResponseEntity.ok(new RapUser())); + + ResponseEntity refreshTokenResponse = mock(ResponseEntity.class); + Map<String, Object> mockedBody = new HashMap<>(); + mockedBody.put("access_token", "<access_token>"); + mockedBody.put("refresh_token", "<refresh_token>"); + mockedBody.put("expires_in", 3600); + when(refreshTokenResponse.getBody()).thenReturn(mockedBody); + + when(refreshTokenRestTemplate.postForEntity(anyString(), any(HttpEntity.class), any())) + .thenReturn(refreshTokenResponse); + + RapUser user = rapClient.getUser("123"); + assertNotNull(user); + + // verifies that token is refreshed + verify(sessionData, times(1)).setAccessToken(eq("<access_token>")); + verify(sessionData, times(1)).setExpiresIn(eq(3600)); + } +}