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

Join implementation changes

parent 9cbdd322
......@@ -93,13 +93,6 @@ public class CLI implements CommandLineRunner {
client.removePermission(getNames(args, 1, args.length - 2), args[args.length - 1]);
System.out.println("Permission removed");
break;
case "prepare-join":
if (args.length != 3) {
displayUsage();
}
client.prepareToJoin(args[1], args[2]);
System.out.println("Join prepared");
break;
default:
displayUsage();
break;
......@@ -113,8 +106,7 @@ public class CLI implements CommandLineRunner {
+ " 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>\n"
+ " prepare-join <from_user_id> <to_user_id>");
+ " delete-permission <name1 name2 name3> <user_id>");
System.exit(0);
}
......
......@@ -116,20 +116,6 @@ public class GmsClient {
restTemplate.exchange(url, HttpMethod.DELETE, getEntity(), Void.class);
}
public void prepareToJoin(String fromUserId, String toUserId) {
String url = UriComponentsBuilder.fromHttpUrl(baseUrl)
.pathSegment("prepare-join")
.toUriString();
Map<String, Object> params = new HashMap<>();
params.put("fromUserId", fromUserId);
params.put("toUserId", toUserId);
HttpEntity<Map<String, Object>> httpEntity = getEntity(params);
restTemplate.exchange(url, HttpMethod.POST, httpEntity, Void.class);
}
private HttpEntity<?> getEntity() {
return new HttpEntity<>(getHeaders());
}
......
......@@ -150,28 +150,6 @@ public class GmsClientTest {
verifyAuthHeaders(entity);
}
@Test
public void testPrepareToJoin() {
String fromUserId = "from_user_id";
String toUserId = "to_user_id";
client.prepareToJoin(fromUserId, toUserId);
ArgumentCaptor<HttpEntity> entityCaptor = ArgumentCaptor.forClass(HttpEntity.class);
verify(restTemplate, times(1)).exchange(eq(BASE_URL + "/ws/prepare-join"),
eq(HttpMethod.POST), entityCaptor.capture(), eq(Void.class));
HttpEntity<?> entity = entityCaptor.getValue();
verifyAuthHeaders(entity);
Map<String, Object> expectedBody = new HashMap<>();
expectedBody.put("fromUserId", fromUserId);
expectedBody.put("toUserId", toUserId);
verifyBody(entity, expectedBody);
}
private void verifyAuthHeaders(HttpEntity<?> entity) {//
String authHeader = entity.getHeaders().getFirst("Authorization");
assertEquals("Basic dGVzdDp0ZXN0", authHeader);
......
......@@ -2,8 +2,12 @@ package it.inaf.ia2.gms;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@Configuration
@EnableTransactionManagement
public class GmsApplication {
public static void main(String[] args) {
......
......@@ -11,7 +11,6 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.store.jwk.JwkTokenStore;
......@@ -45,29 +44,28 @@ public class JWTFilter implements Filter {
Map<String, Object> claims = accessToken.getAdditionalInformation();
String principal = (String) claims.get("sub");
if (principal == null) {
if (claims.get("sub") == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid access token: missing sub claim");
return;
}
ServletRequest wrappedRequest = new ServletRequestWithJWTPrincipal(request, principal);
ServletRequest wrappedRequest = new ServletRequestWithJWTPrincipal(request, claims);
fc.doFilter(wrappedRequest, res);
}
private static class ServletRequestWithJWTPrincipal extends HttpServletRequestWrapper {
private final String principal;
private final Principal principal;
public ServletRequestWithJWTPrincipal(HttpServletRequest request, String principal) {
public ServletRequestWithJWTPrincipal(HttpServletRequest request, Map<String, Object> jwtClaims) {
super(request);
this.principal = principal;
this.principal = new RapPrincipal(jwtClaims);
}
@Override
public Principal getUserPrincipal() {
return new UsernamePasswordAuthenticationToken(principal, null);
return principal;
}
}
}
package it.inaf.ia2.gms.authn;
import java.security.Principal;
import java.util.Map;
public class RapPrincipal implements Principal {
private final String sub;
private final String altSub;
public RapPrincipal(Map<String, Object> jwtClaims) {
sub = (String) jwtClaims.get("sub");
altSub = (String) jwtClaims.get("alt_sub");
}
@Override
public String getName() {
return sub;
}
/**
* Alternative subject identifier: used during a join.
*/
public String getAlternativeName() {
return altSub;
}
}
......@@ -3,7 +3,6 @@ package it.inaf.ia2.gms.controller;
import it.inaf.ia2.gms.exception.BadRequestException;
import it.inaf.ia2.gms.model.request.AddMemberWsRequest;
import it.inaf.ia2.gms.model.request.AddPermissionWsRequest;
import it.inaf.ia2.gms.model.PrepareToJoinRequest;
import it.inaf.ia2.gms.persistence.model.GroupEntity;
import it.inaf.ia2.gms.persistence.model.MembershipEntity;
import it.inaf.ia2.gms.persistence.model.PermissionEntity;
......@@ -112,15 +111,6 @@ public class BasicAuthWebServiceController {
return ResponseEntity.noContent().build();
}
@PostMapping(value = "/prepare-join", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<?> prepareToJoin(@Valid @RequestBody PrepareToJoinRequest request) {
permissionsService.movePermissions(request.getFromUserId(), request.getToUserId());
membersService.moveMemberships(request.getFromUserId(), request.getToUserId());
return ResponseEntity.ok().build();
}
private GroupEntity getGroupByNames(List<String> names) {
return groupsService.findGroupByNames(names)
.orElseThrow(() -> new BadRequestException("Unable to find requested group"));
......
package it.inaf.ia2.gms.controller;
import it.inaf.ia2.gms.authn.RapPrincipal;
import it.inaf.ia2.gms.exception.BadRequestException;
import it.inaf.ia2.gms.persistence.GroupsDAO;
import it.inaf.ia2.gms.persistence.MembershipsDAO;
import it.inaf.ia2.gms.persistence.model.GroupEntity;
import it.inaf.ia2.gms.service.JoinService;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Principal;
......@@ -15,7 +18,9 @@ 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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
......@@ -29,6 +34,9 @@ public class JWTWebServiceController {
@Autowired
private MembershipsDAO membershipsDAO;
@Autowired
private JoinService joinService;
@Autowired
private GroupsDAO groupsDAO;
......@@ -93,4 +101,21 @@ public class JWTWebServiceController {
return String.join(".", names);
}
@PostMapping(value = "/join", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> join(RapPrincipal principal) {
String fromUser = principal.getName();
String toUser = principal.getAlternativeName();
if (toUser == null) {
throw new BadRequestException("Missing alternative subject");
}
joinService.join(fromUser, toUser);
Map<String, String> responseBody = new HashMap<>();
responseBody.put("mergedId", fromUser);
return ResponseEntity.ok(responseBody);
}
}
package it.inaf.ia2.gms.model;
public class PrepareToJoinRequest {
private String fromUserId;
private String toUserId;
public String getFromUserId() {
return fromUserId;
}
public void setFromUserId(String fromUserId) {
this.fromUserId = fromUserId;
}
public String getToUserId() {
return toUserId;
}
public void setToUserId(String toUserId) {
this.toUserId = toUserId;
}
}
package it.inaf.ia2.gms.persistence;
import it.inaf.ia2.gms.persistence.model.MembershipEntity;
import it.inaf.ia2.gms.persistence.model.PermissionEntity;
import java.sql.PreparedStatement;
import java.sql.Types;
import java.util.Collections;
import java.util.Set;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Component
public class JoinDAO {
private static final Logger LOG = LoggerFactory.getLogger(JoinDAO.class);
private final JdbcTemplate jdbcTemplate;
@Autowired
public JoinDAO(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Transactional
public void join(Set<MembershipEntity> membershipsToAdd, Set<PermissionEntity> permissionsToAdd, String userToDelete) {
if (!membershipsToAdd.isEmpty()) {
addMemberships(membershipsToAdd);
}
if (!permissionsToAdd.isEmpty()) {
addPermissions(permissionsToAdd);
}
deleteUserMemberships(userToDelete);
deleteUserPermissions(userToDelete);
}
private void addMemberships(Set<MembershipEntity> membershipsToAdd) {
String sql = "INSERT INTO gms_membership (group_id, user_id) VALUES "
+ String.join(", ", Collections.nCopies(membershipsToAdd.size(), "(?, ?)"));
LOG.trace("Executing {}", sql);
jdbcTemplate.update(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
int i = 0;
for (MembershipEntity membership : membershipsToAdd) {
ps.setString(++i, membership.getGroupId());
ps.setString(++i, membership.getUserId());
}
return ps;
});
}
private void addPermissions(Set<PermissionEntity> permissionsToAdd) {
String sql = "INSERT INTO gms_permission (group_id, user_id, permission, group_path) VALUES "
+ String.join(", ", Collections.nCopies(permissionsToAdd.size(), "(?, ?, ?, ?)")) + "\n"
+ "ON CONFLICT (group_id, user_id) DO UPDATE\n"
+ "SET permission = EXCLUDED.permission";;
LOG.trace("Executing {}", sql);
jdbcTemplate.update(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
int i = 0;
for (PermissionEntity permission : permissionsToAdd) {
ps.setString(++i, permission.getGroupId());
ps.setString(++i, permission.getUserId());
ps.setObject(++i, permission.getPermission().toString(), Types.OTHER);
ps.setObject(++i, permission.getGroupPath(), Types.OTHER);
}
return ps;
});
}
private void deleteUserMemberships(String userId) {
String sql = "DELETE FROM gms_membership WHERE user_id = ?";
LOG.trace("Executing {}", sql);
jdbcTemplate.update(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, userId);
return ps;
});
}
private void deleteUserPermissions(String userId) {
String sql = "DELETE FROM gms_permission WHERE user_id = ?";
LOG.trace("Executing {}", sql);
jdbcTemplate.update(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, userId);
return ps;
});
}
}
......@@ -109,18 +109,6 @@ public class MembershipsDAO {
});
}
public void moveMemberships(String fromUserId, String toUserId) {
String sql = "UPDATE gms_membership SET user_id = ? WHERE user_id = ?";
jdbcTemplate.update(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, toUserId);
ps.setString(2, fromUserId);
return ps;
});
}
public void deleteAllGroupsMembership(List<String> groupIds) {
String sql = "DELETE FROM gms_membership WHERE group_id IN ("
......
......@@ -144,18 +144,6 @@ public class PermissionsDAO {
});
}
public void movePermissions(String fromUserId, String toUserId) {
String sql = "UPDATE gms_permission SET user_id = ? WHERE user_id = ?";
jdbcTemplate.update(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, toUserId);
ps.setString(2, fromUserId);
return ps;
});
}
public void deleteAllGroupsPermissions(List<String> groupIds) {
String sql = "DELETE FROM gms_permission WHERE group_id IN ("
......
package it.inaf.ia2.gms.persistence.model;
import java.util.Objects;
import javax.validation.constraints.NotEmpty;
public class MembershipEntity {
......@@ -24,4 +25,30 @@ public class MembershipEntity {
public void setUserId(String userId) {
this.userId = userId;
}
@Override
public int hashCode() {
int hash = 5;
hash = 67 * hash + Objects.hashCode(this.groupId);
hash = 67 * hash + Objects.hashCode(this.userId);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final MembershipEntity other = (MembershipEntity) obj;
if (!Objects.equals(this.groupId, other.groupId)) {
return false;
}
return Objects.equals(this.userId, other.userId);
}
}
package it.inaf.ia2.gms.persistence.model;
import it.inaf.ia2.gms.model.Permission;
import java.util.Objects;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
......@@ -47,4 +48,38 @@ public class PermissionEntity {
public void setGroupPath(String groupPath) {
this.groupPath = groupPath;
}
@Override
public int hashCode() {
int hash = 5;
hash = 41 * hash + Objects.hashCode(this.userId);
hash = 41 * hash + Objects.hashCode(this.groupId);
hash = 41 * hash + Objects.hashCode(this.permission);
hash = 41 * hash + Objects.hashCode(this.groupPath);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PermissionEntity other = (PermissionEntity) obj;
if (!Objects.equals(this.userId, other.userId)) {
return false;
}
if (!Objects.equals(this.groupId, other.groupId)) {
return false;
}
if (!Objects.equals(this.groupPath, other.groupPath)) {
return false;
}
return this.permission == other.permission;
}
}
package it.inaf.ia2.gms.service;
import it.inaf.ia2.gms.model.Permission;
import it.inaf.ia2.gms.persistence.JoinDAO;
import it.inaf.ia2.gms.persistence.MembershipsDAO;
import it.inaf.ia2.gms.persistence.PermissionsDAO;
import it.inaf.ia2.gms.persistence.model.MembershipEntity;
import it.inaf.ia2.gms.persistence.model.PermissionEntity;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class JoinService {
@Autowired
private MembershipsDAO membershipsDAO;
@Autowired
private PermissionsDAO permissionsDAO;
@Autowired
private JoinDAO joinDAO;
public void join(String userId1, String userId2) {
Set<MembershipEntity> existingMemberships
= membershipsDAO.getUserMemberships(userId1).stream()
.map(g -> getMembershipEntity(g.getId(), userId1))
.collect(Collectors.toSet());
Set<MembershipEntity> membershipsToAdd
= membershipsDAO.getUserMemberships(userId2).stream()
.map(g -> getMembershipEntity(g.getId(), userId1))
.filter(m -> !existingMemberships.contains(m))
.collect(Collectors.toSet());
Set<PermissionEntity> existingPermissions
= permissionsDAO.findUserPermissions(userId1).stream()
.collect(Collectors.toSet());
Set<PermissionEntity> permissionsToAdd
= permissionsDAO.findUserPermissions(userId2).stream()
.map(p -> {
p.setUserId(userId1);
return p;
})
.filter(p -> isPermissionToAdd(existingPermissions, p))
.collect(Collectors.toSet());
joinDAO.join(membershipsToAdd, permissionsToAdd, userId2);
}
private MembershipEntity getMembershipEntity(String groupId, String userId) {
MembershipEntity entity = new MembershipEntity();
entity.setGroupId(groupId);
entity.setUserId(userId);
return entity;
}
private boolean isPermissionToAdd(Set<PermissionEntity> existingPermissions, PermissionEntity permissionToCheck) {
for (PermissionEntity permission : existingPermissions) {
if (permission.getGroupId().equals(permissionToCheck.getGroupId())
&& permission.getUserId().equals(permissionToCheck.getUserId())) {
if (permission.getPermission() == permissionToCheck.getPermission()) {
return false;
}
Permission strongerPermission = Permission.addPermission(
permission.getPermission(), permissionToCheck.getPermission());
return permission.getPermission() != strongerPermission;
}
}
return true;
}
}
......@@ -42,8 +42,4 @@ public class MembersService {
public void removeMember(String groupId, String userId) {