Commit 4e96f2f0 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Merge branch 'master' into franco

parents b39b7cf9 d9c5b66f
......@@ -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>
......
......@@ -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";
}
......
......@@ -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;
......
......@@ -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());
......
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) {
......
......@@ -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) {
......
......@@ -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;
}
}
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;
}
}
......
......@@ -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
......
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