JD-710: user organization context (#53)

This commit is contained in:
vitaxa 2021-10-01 15:29:49 +03:00 committed by GitHub
parent de838229b6
commit 790b988e55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 248 additions and 38 deletions

7
Jenkinsfile vendored
View File

@ -5,12 +5,11 @@ build('org-manager', 'java-maven') {
def javaServicePipeline def javaServicePipeline
runStage('load JavaService pipeline') { runStage('load JavaService pipeline') {
javaServicePipeline = load("build_utils/jenkins_lib/pipeJavaService.groovy") javaServicePipeline = load("build_utils/jenkins_lib/pipeJavaServiceInsideDocker.groovy")
} }
def serviceName = env.REPO_NAME def serviceName = env.REPO_NAME
def mvnArgs = '-DjvmArgs="-Xmx256m"' def mvnArgs = '-DjvmArgs="-Xmx256m"'
def useJava11 = true
javaServicePipeline(serviceName, useJava11, mvnArgs) javaServicePipeline(serviceName, mvnArgs)
} }

@ -1 +1 @@
Subproject commit a7655bc60c877a65cdfe3d9b668021d970d88a76 Subproject commit be44d69fc87b22a0bb82d98d6eae7658d1647f98

12
pom.xml
View File

@ -6,7 +6,7 @@
<parent> <parent>
<groupId>com.rbkmoney</groupId> <groupId>com.rbkmoney</groupId>
<artifactId>service-parent-pom</artifactId> <artifactId>service-parent-pom</artifactId>
<version>1.2.10</version> <version>2.0.8</version>
</parent> </parent>
<artifactId>org-manager</artifactId> <artifactId>org-manager</artifactId>
@ -18,18 +18,15 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>11</java.version>
<server.port>8022</server.port> <server.port>8022</server.port>
<server.rest.port>8080</server.rest.port> <server.rest.port>8080</server.rest.port>
<management.port>8023</management.port> <management.port>8023</management.port>
<exposed.ports>${server.port} ${server.rest.port} ${management.port}</exposed.ports> <exposed.ports>${server.port} ${server.rest.port} ${management.port}</exposed.ports>
<dockerfile.base.service.tag>c0612d6052ac049496b72a23a04acb142035f249</dockerfile.base.service.tag> <dockerfile.registry>${env.REGISTRY}</dockerfile.registry>
<dockerfile.registry>dr2.rbkmoney.com</dockerfile.registry> <spring-security.version>5.5.2</spring-security.version>
<!--because of https://github.com/spring-projects/spring-security/issues/9787-->
<spring-security.version>5.4.6</spring-security.version>
<keycloak.version>14.0.0</keycloak.version> <keycloak.version>14.0.0</keycloak.version>
<schedlock.version>4.14.0</schedlock.version> <schedlock.version>4.14.0</schedlock.version>
<swag.organizations.version>1.18-38ef431-server</swag.organizations.version> <swag.organizations.version>1.19-8707f87-server</swag.organizations.version>
</properties> </properties>
<dependencies> <dependencies>
@ -280,7 +277,6 @@
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.1.RELEASE</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>

View File

@ -5,15 +5,15 @@ import com.rbkmoney.orgmanager.service.OrganizationService;
import com.rbkmoney.orgmanager.service.ResourceAccessService; import com.rbkmoney.orgmanager.service.ResourceAccessService;
import com.rbkmoney.orgmanager.service.dto.ResourceDto; import com.rbkmoney.orgmanager.service.dto.ResourceDto;
import com.rbkmoney.swag.organizations.api.UserApi; import com.rbkmoney.swag.organizations.api.UserApi;
import com.rbkmoney.swag.organizations.model.OrganizationJoinRequest; import com.rbkmoney.swag.organizations.model.*;
import com.rbkmoney.swag.organizations.model.OrganizationMembership;
import com.rbkmoney.swag.organizations.model.OrganizationSearchResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@Slf4j @Slf4j
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@ -74,4 +74,32 @@ public class UserController implements UserApi {
organizationService.findAllOrganizations(accessToken.getSubject(), limit, continuationToken); organizationService.findAllOrganizations(accessToken.getSubject(), limit, continuationToken);
return ResponseEntity.ok(organizationSearchResult); return ResponseEntity.ok(organizationSearchResult);
} }
@Override
public ResponseEntity<MemberContext> getContext(String requestId) {
log.info("Get user context. requestId={}", requestId);
resourceAccessService.checkRights();
AccessToken accessToken = keycloakService.getAccessToken();
MemberContext memberContext = organizationService.findMemberContext(accessToken.getSubject());
return ResponseEntity.ok(memberContext);
}
@Override
public ResponseEntity<Void> switchContext(String requestId,
@Valid OrganizationSwitchRequest organizationSwitchRequest) {
log.info("Switch user context. requestId={}, body={}", requestId, organizationSwitchRequest);
ResourceDto resource = ResourceDto.builder()
.orgId(organizationSwitchRequest.getOrganizationId())
.build();
resourceAccessService.checkRights(resource);
AccessToken accessToken = keycloakService.getAccessToken();
organizationService.switchMemberContext(
accessToken.getSubject(),
organizationSwitchRequest.getOrganizationId()
);
return ResponseEntity.noContent().build();
}
} }

View File

@ -0,0 +1,31 @@
package com.rbkmoney.orgmanager.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.io.Serializable;
@Entity
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "member_context")
public class MemberContextEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "organization_id", referencedColumnName = "id")
private OrganizationEntity organizationEntity;
@OneToOne
@JoinColumn(name = "member_id", referencedColumnName = "id")
private MemberEntity memberEntity;
}

View File

@ -0,0 +1,14 @@
package com.rbkmoney.orgmanager.repository;
import com.rbkmoney.orgmanager.entity.MemberContextEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface MemberContextRepository extends JpaRepository<MemberContextEntity, String> {
Optional<MemberContextEntity> findByMemberEntityId(String memberId);
}

View File

@ -57,7 +57,7 @@ public class InvitationService {
.build(); .build();
} }
Invitation invitation = invitationConverter.toDomain(entity.get()); Invitation invitation = invitationConverter.toDomain(entity.orElseThrow());
return ResponseEntity return ResponseEntity
.status(HttpStatus.OK) .status(HttpStatus.OK)
.body(invitation); .body(invitation);

View File

@ -47,7 +47,7 @@ public class OrganizationRoleService {
.build(); .build();
} }
List<Role> roles = entity.get().getRoles() List<Role> roles = entity.orElseThrow().getRoles()
.stream() .stream()
.map(organizationRoleConverter::toDomain) .map(organizationRoleConverter::toDomain)
.collect(toList()); .collect(toList());

View File

@ -3,13 +3,11 @@ package com.rbkmoney.orgmanager.service;
import com.rbkmoney.orgmanager.converter.MemberConverter; import com.rbkmoney.orgmanager.converter.MemberConverter;
import com.rbkmoney.orgmanager.converter.MemberRoleConverter; import com.rbkmoney.orgmanager.converter.MemberRoleConverter;
import com.rbkmoney.orgmanager.converter.OrganizationConverter; import com.rbkmoney.orgmanager.converter.OrganizationConverter;
import com.rbkmoney.orgmanager.entity.InvitationEntity; import com.rbkmoney.orgmanager.entity.*;
import com.rbkmoney.orgmanager.entity.MemberEntity;
import com.rbkmoney.orgmanager.entity.MemberRoleEntity;
import com.rbkmoney.orgmanager.entity.OrganizationEntity;
import com.rbkmoney.orgmanager.exception.AccessDeniedException; import com.rbkmoney.orgmanager.exception.AccessDeniedException;
import com.rbkmoney.orgmanager.exception.LastRoleException; import com.rbkmoney.orgmanager.exception.LastRoleException;
import com.rbkmoney.orgmanager.exception.ResourceNotFoundException; import com.rbkmoney.orgmanager.exception.ResourceNotFoundException;
import com.rbkmoney.orgmanager.repository.MemberContextRepository;
import com.rbkmoney.orgmanager.repository.MemberRepository; import com.rbkmoney.orgmanager.repository.MemberRepository;
import com.rbkmoney.orgmanager.repository.OrganizationRepository; import com.rbkmoney.orgmanager.repository.OrganizationRepository;
import com.rbkmoney.orgmanager.service.dto.MemberWithRoleDto; import com.rbkmoney.orgmanager.service.dto.MemberWithRoleDto;
@ -43,6 +41,7 @@ public class OrganizationService {
private final MemberConverter memberConverter; private final MemberConverter memberConverter;
private final MemberRoleConverter memberRoleConverter; private final MemberRoleConverter memberRoleConverter;
private final MemberRepository memberRepository; private final MemberRepository memberRepository;
private final MemberContextRepository memberContextRepository;
private final InvitationService invitationService; private final InvitationService invitationService;
private final MemberRoleService memberRoleService; private final MemberRoleService memberRoleService;
@ -72,15 +71,14 @@ public class OrganizationService {
public ResponseEntity<Organization> get(String orgId) { public ResponseEntity<Organization> get(String orgId) {
Optional<OrganizationEntity> entity = organizationRepository.findById(orgId); Optional<OrganizationEntity> entity = organizationRepository.findById(orgId);
if (entity.isEmpty()) { if (entity.isPresent()) {
return ResponseEntity Organization organization = organizationConverter.toDomain(entity.get());
.status(HttpStatus.NOT_FOUND)
.build(); return ResponseEntity.ok(organization);
} }
return ResponseEntity
Organization organization = organizationConverter.toDomain(entity.get()); .status(HttpStatus.NOT_FOUND)
.build();
return ResponseEntity.ok(organization);
} }
@ -214,8 +212,10 @@ public class OrganizationService {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
organizationEntityOptional.get().getMembers() organizationEntityOptional.ifPresent(organizationEntity -> {
.removeIf(memberEntity -> memberEntity.getId().equals(memberEntityOptional.get().getId())); organizationEntity.getMembers()
.removeIf(memberEntity -> memberEntity.getId().equals(memberEntityOptional.get().getId()));
});
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@ -235,8 +235,8 @@ public class OrganizationService {
} }
OrganizationMembership organizationMembership = new OrganizationMembership(); OrganizationMembership organizationMembership = new OrganizationMembership();
organizationMembership.setMember(memberConverter.toDomain(memberEntityOptional.get())); organizationMembership.setMember(memberConverter.toDomain(memberEntityOptional.orElseThrow()));
organizationMembership.setOrg(organizationConverter.toDomain(organizationEntityOptional.get())); organizationMembership.setOrg(organizationConverter.toDomain(organizationEntityOptional.orElseThrow()));
return ResponseEntity.ok(organizationMembership); return ResponseEntity.ok(organizationMembership);
} }
@ -262,6 +262,36 @@ public class OrganizationService {
return organizationMembership; return organizationMembership;
} }
@Transactional
public void switchMemberContext(String userId, String organizationId) {
OrganizationEntity organizationEntity = organizationRepository.findById(organizationId)
.orElseThrow(ResourceNotFoundException::new);
Optional<MemberContextEntity> memberContextEntityOptional =
memberContextRepository.findByMemberEntityId(userId);
if (memberContextEntityOptional.isPresent()) {
MemberContextEntity memberContextEntity = memberContextEntityOptional.get();
memberContextEntity.setOrganizationEntity(organizationEntity);
memberContextRepository.save(memberContextEntity);
} else {
MemberEntity memberEntity = memberRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("Can't find member. Unknown userId=" + userId));
MemberContextEntity memberContextEntity = new MemberContextEntity();
memberContextEntity.setOrganizationEntity(organizationEntity);
memberContextEntity.setMemberEntity(memberEntity);
memberContextRepository.save(memberContextEntity);
}
}
public MemberContext findMemberContext(String userId) {
MemberContextEntity memberContextEntity = memberContextRepository.findByMemberEntityId(userId)
.orElseThrow(ResourceNotFoundException::new);
MemberContext memberContext = new MemberContext();
memberContext.setOrganizationId(memberContextEntity.getOrganizationEntity().getId());
return memberContext;
}
private MemberEntity findOrCreateMember(String userId, String userEmail) { private MemberEntity findOrCreateMember(String userId, String userEmail) {
return memberRepository.findById(userId) return memberRepository.findById(userId)
.orElseGet(() -> { .orElseGet(() -> {

View File

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS org_manager.member_context
(
id BIGSERIAL NOT NULL,
member_id CHARACTER VARYING NOT NULL,
organization_id CHARACTER VARYING NOT NULL,
CONSTRAINT member_context_pkey PRIMARY KEY (id),
CONSTRAINT member_context_to_member_fkey FOREIGN KEY (member_id) REFERENCES org_manager.member (id),
CONSTRAINT member_context_to_organization_fkey FOREIGN KEY (organization_id) REFERENCES org_manager.organization (id)
);

View File

@ -1,9 +1,7 @@
package com.rbkmoney.orgmanager.controller; package com.rbkmoney.orgmanager.controller;
import com.rbkmoney.orgmanager.entity.InvitationEntity; import com.fasterxml.jackson.core.JsonProcessingException;
import com.rbkmoney.orgmanager.entity.MemberEntity; import com.rbkmoney.orgmanager.entity.*;
import com.rbkmoney.orgmanager.entity.MemberRoleEntity;
import com.rbkmoney.orgmanager.entity.OrganizationEntity;
import com.rbkmoney.orgmanager.exception.AccessDeniedException; import com.rbkmoney.orgmanager.exception.AccessDeniedException;
import com.rbkmoney.orgmanager.exception.ResourceNotFoundException; import com.rbkmoney.orgmanager.exception.ResourceNotFoundException;
import com.rbkmoney.orgmanager.service.dto.ResourceDto; import com.rbkmoney.orgmanager.service.dto.ResourceDto;
@ -16,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -311,4 +310,103 @@ public class UserControllerTest extends AbstractControllerTest {
assertEquals(2, organizationSearchResultThird.getResult().size()); assertEquals(2, organizationSearchResultThird.getResult().size());
assertNull(organizationSearchResultThird.getContinuationToken()); assertNull(organizationSearchResultThird.getContinuationToken());
} }
@Test
void switchOrganizationWithNewContextCreation() throws Exception {
String userId = getUserFromToken();
MemberEntity memberEntity = memberRepository.save(testMemberEntity(userId));
OrganizationEntity organizationEntity = organizationRepository.save(buildOrganization());
String jwtToken = generateRbkAdminJwt();
OrganizationSwitchRequest organizationSwitchRequest = new OrganizationSwitchRequest();
organizationSwitchRequest.setOrganizationId(organizationEntity.getId());
mockMvc.perform(put("/user/context")
.accept(MediaType.APPLICATION_JSON)
.contentType("application/json")
.header("Authorization", "Bearer " + jwtToken)
.header("X-Request-ID", "testRequestId")
.content(objectMapper.writeValueAsString(organizationSwitchRequest))
)
.andExpect(status().isNoContent());
Optional<MemberContextEntity> memberContextEntityOptional =
memberContextRepository.findByMemberEntityId(userId);
assertTrue(memberContextEntityOptional.isPresent());
assertEquals(userId, memberContextEntityOptional.get().getMemberEntity().getId());
assertEquals(organizationEntity.getId(), memberContextEntityOptional.get().getOrganizationEntity().getId());
}
@Test
void switchOrganizationOnUnknown() throws Exception {
String userId = getUserFromToken();
MemberEntity memberEntity = memberRepository.save(testMemberEntity(userId));
String jwtToken = generateRbkAdminJwt();
OrganizationSwitchRequest organizationSwitchRequest = new OrganizationSwitchRequest();
organizationSwitchRequest.setOrganizationId("testOrgId");
mockMvc.perform(put("/user/context")
.accept(MediaType.APPLICATION_JSON)
.contentType("application/json")
.header("Authorization", "Bearer " + jwtToken)
.header("X-Request-ID", "testRequestId")
.content(objectMapper.writeValueAsString(organizationSwitchRequest))
)
.andExpect(status().isNotFound());
}
@Test
void switchOrganizationWithExistsContext() throws Exception {
String userId = getUserFromToken();
MemberEntity memberEntity = memberRepository.save(testMemberEntity(userId));
OrganizationEntity organizationEntity = organizationRepository.save(buildOrganization());
MemberContextEntity memberContextEntity = memberContextRepository.save(
MemberContextEntity.builder()
.memberEntity(memberEntity)
.organizationEntity(organizationEntity)
.build()
);
OrganizationEntity newOrganizationEntity = organizationRepository.save(buildOrganization());
String jwtToken = generateRbkAdminJwt();
OrganizationSwitchRequest organizationSwitchRequest = new OrganizationSwitchRequest();
organizationSwitchRequest.setOrganizationId(newOrganizationEntity.getId());
mockMvc.perform(put("/user/context")
.accept(MediaType.APPLICATION_JSON)
.contentType("application/json")
.header("Authorization", "Bearer " + jwtToken)
.header("X-Request-ID", "testRequestId")
.content(objectMapper.writeValueAsString(organizationSwitchRequest))
)
.andExpect(status().isNoContent());
Optional<MemberContextEntity> memberContextEntityOptional =
memberContextRepository.findByMemberEntityId(userId);
assertTrue(memberContextEntityOptional.isPresent());
assertEquals(newOrganizationEntity.getId(), memberContextEntityOptional.get().getOrganizationEntity().getId());
}
@Test
void getMemberContext() throws Exception {
String userId = getUserFromToken();
MemberEntity memberEntity = memberRepository.save(testMemberEntity(userId));
OrganizationEntity organizationEntity = organizationRepository.save(buildOrganization());
MemberContextEntity memberContextEntity = memberContextRepository.save(
MemberContextEntity.builder()
.memberEntity(memberEntity)
.organizationEntity(organizationEntity)
.build()
);
String jwtToken = generateRbkAdminJwt();
MvcResult mvcResult = mockMvc.perform(get("/user/context")
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + jwtToken)
.header("X-Request-ID", "testRequestId")
)
.andExpect(status().isOk())
.andReturn();
System.out.println(mvcResult);
}
} }

View File

@ -37,10 +37,14 @@ public abstract class AbstractRepositoryTest {
@Autowired @Autowired
protected OrganizationRoleRepository organizationRoleRepository; protected OrganizationRoleRepository organizationRoleRepository;
@Autowired
protected MemberContextRepository memberContextRepository;
@Transactional @Transactional
@BeforeEach @BeforeEach
public void setUp() throws Exception { public void setUp() throws Exception {
invitationRepository.deleteAll(); invitationRepository.deleteAll();
memberContextRepository.deleteAll();
organizationRepository.deleteAll(); organizationRepository.deleteAll();
var members = memberRepository.findAll(); var members = memberRepository.findAll();
members.forEach(it -> it.getRoles().clear()); members.forEach(it -> it.getRoles().clear());