Commit c2fbedcb authored by Rostislav Vatolin [SLB]'s avatar Rostislav Vatolin [SLB] Committed by Jason
Browse files

Add tenant init API

parent 90d6384d
......@@ -82,7 +82,6 @@ The following software have components provided under the terms of this license:
- Google HTTP Client Library for Java (from https://github.com/google/google-http-java-client.git)
- Google OAuth Client Library for Java (from )
- Gson (from https://github.com/google/gson)
- Gson (from https://github.com/google/gson)
- Guava InternalFutureFailureAccess and InternalFutures (from )
- Guava ListenableFuture only (from )
- Guava: Google Core Libraries for Java (from https://github.com/google/guava.git)
......@@ -159,8 +158,8 @@ The following software have components provided under the terms of this license:
- Microsoft Application Insights Log4j 2 Appender (from https://github.com/Microsoft/ApplicationInsights-Java)
- Microsoft Azure Netty HTTP Client Library (from https://github.com/Azure/azure-sdk-for-java)
- Microsoft Azure SDK for SQL API of Azure Cosmos DB Service (from https://github.com/Azure/azure-sdk-for-java)
- Mockito (from http://mockito.org)
- Mockito (from http://www.mockito.org)
- Mockito (from http://mockito.org)
- Netty Reactive Streams Implementation (from )
- Netty/All-in-One (from )
- Netty/Buffer (from http://netty.io/)
......
......@@ -2,7 +2,6 @@ package org.opengroup.osdu.entitlements.v2;
import org.springframework.beans.factory.annotation.Value;
import java.util.ArrayList;
import java.util.List;
public abstract class AppProperties {
......@@ -29,11 +28,13 @@ public abstract class AppProperties {
return httpAccepted;
}
public List<String> getInitialGroups() {
List<String> initialGroups = new ArrayList<>(3);
initialGroups.add("/provisioning/groups/datalake_user_groups.json");
initialGroups.add("/provisioning/groups/datalake_service_groups.json");
initialGroups.add("/provisioning/groups/data_groups.json");
return initialGroups;
}
/**
* @return a list containing paths of configuration files
*/
public abstract List<String> getInitialGroups();
/**
* @return a path of configuration file
*/
public abstract String getGroupsOfServicePrincipal();
}
package org.opengroup.osdu.entitlements.v2.api;
import org.opengroup.osdu.entitlements.v2.service.TenantInitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class InitApi {
@Autowired
private TenantInitService tenantInitService;
@PostMapping("/tenant-provisioning")
@PreAuthorize("@authorizationFilter.hasAnyPermission()")
public ResponseEntity<Void> initiateTenant() {
tenantInitService.createDefaultGroups();
tenantInitService.bootstrapServicePrincipal();
return ResponseEntity.ok().build();
}
}
......@@ -20,18 +20,15 @@ import java.util.Set;
public class KeySvcAccBeanConfiguration {
private final FileReaderService fileReaderService;
private final AppProperties appProperties;
private final RequestInfo requestInfo;
private final AppProperties appProperties;
private Map<String, Set<String>> svcAccGroupConfig;
@PostConstruct
private void init() {
this.svcAccGroupConfig = new HashMap<>();
loadConfiguration("/provisioning/accounts/datalake_ops.json");
loadConfiguration("/provisioning/accounts/datalake_viewers.json");
loadConfiguration("/provisioning/accounts/datalake_root.json");
loadConfiguration("/provisioning/accounts/apigateway_serviceaccount.json");
loadConfiguration(appProperties.getGroupsOfServicePrincipal());
}
public boolean isKeySvcAccountInBootstrapGroup(final String groupEmail, final String memberEmail) {
......@@ -39,17 +36,17 @@ public class KeySvcAccBeanConfiguration {
}
public Set<String> getServiceAccountGroups(final String email) {
if (isDatafierServiceAccount(email)) {
return this.svcAccGroupConfig.computeIfAbsent("DATAFIEREMAIL", k -> new HashSet<>());
if (isServicePrincipalAccount(email)) {
return this.svcAccGroupConfig.computeIfAbsent("SERVICE_PRINCIPAL", k -> new HashSet<>());
}
return new HashSet<>();
}
public boolean isKeyServiceAccount(final String email) {
return isDatafierServiceAccount(email);
return isServicePrincipalAccount(email);
}
private boolean isDatafierServiceAccount(final String email) {
private boolean isServicePrincipalAccount(final String email) {
return email.equalsIgnoreCase(requestInfo.getTenantInfo().getServiceAccount());
}
......@@ -62,8 +59,7 @@ public class KeySvcAccBeanConfiguration {
}
private JsonObject getUserJsonObject(final String fileContent) {
return new JsonParser()
.parse(fileContent)
return JsonParser.parseString(fileContent)
.getAsJsonObject()
.get("users")
.getAsJsonArray()
......@@ -74,12 +70,11 @@ public class KeySvcAccBeanConfiguration {
private Set<String> getGroupNamesForOwner(final String fileContent) {
Set<String> groupNames = new HashSet<>();
JsonArray array = new JsonParser()
.parse(fileContent)
JsonArray array = JsonParser.parseString(fileContent)
.getAsJsonObject()
.get("ownersOf")
.getAsJsonArray();
array.forEach(element -> groupNames.add(element.getAsJsonObject().get("groupName").getAsString()));
return groupNames;
}
}
\ No newline at end of file
}
package org.opengroup.osdu.entitlements.v2.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.AllArgsConstructor;
import org.opengroup.osdu.core.common.logging.JaxRsDpsLog;
import org.opengroup.osdu.core.common.model.http.AppException;
import org.opengroup.osdu.core.common.model.http.RequestInfo;
import org.opengroup.osdu.entitlements.v2.AppProperties;
import org.opengroup.osdu.entitlements.v2.model.EntityNode;
import org.opengroup.osdu.entitlements.v2.model.Role;
import org.opengroup.osdu.entitlements.v2.model.addmember.AddMemberDto;
import org.opengroup.osdu.entitlements.v2.model.addmember.AddMemberServiceDto;
import org.opengroup.osdu.entitlements.v2.model.creategroup.CreateGroupDto;
import org.opengroup.osdu.entitlements.v2.model.creategroup.CreateGroupServiceDto;
import org.opengroup.osdu.entitlements.v2.util.FileReaderService;
import org.opengroup.osdu.entitlements.v2.util.RequestInfoUtilService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
@AllArgsConstructor
public class TenantInitService {
private final JaxRsDpsLog log;
private final RequestInfo requestInfo;
private final AppProperties appProperties;
private final AddMemberService addMemberService;
private final FileReaderService fileReaderService;
private final CreateGroupService createGroupService;
private final RequestInfoUtilService requestInfoUtilService;
public void createDefaultGroups() {
appProperties.getInitialGroups().forEach(this::bootstrapGroups);
}
public void bootstrapServicePrincipal() {
final Map<String, String> userEmails = createUserEmails();
final String fileContent = fileReaderService.readFile(appProperties.getGroupsOfServicePrincipal());
final JsonObject userElement = getUserJsonObject(fileContent);
final String emailKey = userElement.get("email").getAsString();
final String role = userElement.get("role").getAsString();
final List<String> groupNames = getGroupNamesForOwner(fileContent);
final AddMemberDto addMemberDto = AddMemberDto.builder()
.email(userEmails.get(emailKey))
.role(Role.valueOf(role.toUpperCase()))
.build();
String partitionId = requestInfo.getHeaders().getPartitionId();
String partitionDomain = requestInfoUtilService.getDomain(partitionId);
final String requesterId = requestInfoUtilService.getUserId(requestInfo.getHeaders());
groupNames.stream()
.map(name -> createEmail(name, partitionDomain))
.forEach(groupId -> {
AddMemberServiceDto addMemberServiceDto = AddMemberServiceDto.builder()
.groupEmail(groupId)
.partitionId(partitionId)
.requesterId(requesterId)
.build();
addMemberToGroup(addMemberDto, addMemberServiceDto);
});
}
private void bootstrapGroups(final String fileName) {
final JsonArray array = getGroupsFromJson(fileName);
List<EntityNode> result = new ArrayList<>();
array.forEach(element -> result.add(createGroup(element)));
final String requesterId = requestInfoUtilService.getUserId(requestInfo.getHeaders());
final String partitionId = requestInfo.getHeaders().getPartitionId();
final String partitionDomain = requestInfoUtilService.getDomain(partitionId);
final CreateGroupServiceDto createGroupServiceDto = CreateGroupServiceDto.builder()
.requesterId(requesterId)
.partitionId(partitionId)
.partitionDomain(partitionDomain)
.build();
final Map<String, String> groupIdsByName = new HashMap<>();
result.forEach(group -> groupIdsByName.put(group.getName().toLowerCase(), createGroup(group, createGroupServiceDto)));
getMembersPerGroup(array).forEach((key, value) -> {
AddMemberServiceDto addMemberServiceDto = AddMemberServiceDto.builder()
.groupEmail(groupIdsByName.get(key.toLowerCase()))
.partitionId(partitionId)
.requesterId(requesterId)
.build();
value.forEach(member -> {
AddMemberDto addMemberDto = AddMemberDto.builder()
.email(createEmail(member, partitionDomain))
.role(Role.MEMBER)
.build();
addMemberToGroup(addMemberDto, addMemberServiceDto);
});
});
}
private JsonArray getGroupsFromJson(final String fileName) {
String fileContent = fileReaderService.readFile(fileName);
return JsonParser.parseString(fileContent)
.getAsJsonObject()
.get("groups")
.getAsJsonArray();
}
private EntityNode createGroup(final JsonElement element) {
final JsonObject jsonObject = element.getAsJsonObject();
final String name = jsonObject.get("name").getAsString();
final String desc = jsonObject.get("description").getAsString();
String partitionIdHeader = requestInfo.getHeaders().getPartitionId();
String partitionDomain = requestInfoUtilService.getDomain(partitionIdHeader);
return CreateGroupDto.createGroupNode(new CreateGroupDto(name, desc), partitionDomain, partitionIdHeader);
}
private Map<String, List<String>> getMembersPerGroup(final JsonArray array) {
Map<String, List<String>> membersPerGroup = new LinkedHashMap<>();
array.forEach(element -> {
List<String> members = getMembers(element);
if (!members.isEmpty()) {
membersPerGroup.put(getGroupName(element), members);
}
});
return membersPerGroup;
}
private List<String> getMembers(final JsonElement element) {
final List<String> members = new ArrayList<>();
JsonElement membersElement = element.getAsJsonObject().get("members");
if (membersElement == null) {
return members;
}
membersElement.getAsJsonArray()
.forEach(jsonElement -> members.add(jsonElement.getAsJsonObject().get("name").getAsString()));
return members;
}
private String getGroupName(final JsonElement element) {
return element.getAsJsonObject().get("name").getAsString();
}
private void addMemberToGroup(final AddMemberDto addMemberDto, final AddMemberServiceDto addMemberServiceDto) {
try {
addMemberService.run(addMemberDto, addMemberServiceDto);
} catch (Exception e) {
if (isNotConflictException(e, "is already a member of group")) {
log.error(String.format("Error at adding member (%s) to a group (%s) in partition %s", addMemberDto.getEmail(),
addMemberServiceDto.getGroupEmail(), addMemberServiceDto.getPartitionId()), e);
throw new AppException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), "Cannot add member to a group");
}
}
}
private boolean isNotConflictException(Exception e, final String expectedErrorMessage) {
return !isConflictException(e, expectedErrorMessage);
}
private boolean isConflictException(Exception e, final String expectedErrorMessage) {
return e instanceof AppException
&& ((AppException)e).getError().getCode() == HttpStatus.CONFLICT.value()
&& ((AppException)e).getError().getMessage().contains(expectedErrorMessage);
}
private String createEmail(String name, String partitionDomain) {
return String.format("%s@%s", name.toLowerCase(), partitionDomain.toLowerCase());
}
private String createGroup(final EntityNode group, final CreateGroupServiceDto createGroupServiceDto) {
try {
return createGroupService.run(group, createGroupServiceDto).getNodeId();
} catch (final Exception e) {
if (isNotConflictException(e, "This group already exists")) {
log.error(String.format("Error creating a group: %s in partition %s", group.getName(), createGroupServiceDto.getPartitionId()), e);
throw new AppException(HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), "Cannot create new group in DB");
}
return group.getNodeId();
}
}
private Map<String, String> createUserEmails() {
Map<String, String> userEmails = new HashMap<>();
userEmails.put("SERVICE_PRINCIPAL", requestInfo.getTenantInfo().getServiceAccount());
return userEmails;
}
private JsonObject getUserJsonObject(String fileContent) {
return JsonParser.parseString(fileContent)
.getAsJsonObject()
.get("users")
.getAsJsonArray()
.iterator()
.next()
.getAsJsonObject();
}
private List<String> getGroupNamesForOwner(final String fileContent) {
final List<String> groupNames = new ArrayList<>();
final JsonArray array = JsonParser.parseString(fileContent)
.getAsJsonObject()
.get("ownersOf")
.getAsJsonArray();
array.forEach(element -> groupNames.add(element.getAsJsonObject().get("groupName").getAsString()));
return groupNames;
}
}
package org.opengroup.osdu.entitlements.v2.spi.tenantinfo;
public interface TenantInfoRepo {
String getServiceAccountOrServicePrincipal(String partitionId);
}
{
"users": [
{
"email": "GATEWAYSVCDESID",
"role": "OWNER"
}
],
"ownersOf": [
{
"groupName": "users"
},
{
"groupName": "service.entitlements.admin"
},
{
"groupName": "service.entitlements.user"
}
]
}
{
"users": [
{
"email": "DATAFIEREMAIL",
"role": "OWNER"
}
],
"ownersOf": [
{
"groupName": "users.data.root"
}
]
}
{
"users": [
{
"email": "SLISVCEMAIL",
"role": "OWNER"
}
],
"ownersOf": [
{
"groupName": "users.datalake.viewers"
},
{
"groupName": "users"
}
]
}
......@@ -4,12 +4,23 @@ import org.opengroup.osdu.entitlements.v2.AppProperties;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import java.util.List;
@TestConfiguration
public class AppPropertiesTestConfiguration {
@Bean
public AppProperties appProperties() {
return new AppProperties() {
@Override
public List<String> getInitialGroups() {
return null;
}
@Override
public String getGroupsOfServicePrincipal() {
return null;
}
};
}
}
......@@ -37,27 +37,10 @@ public class KeySvcAccBeanConfigurationTests {
private TenantInfo tenantInfo;
private final String GATEWAYSVCDESID = "{\n" +
private final String SERVICE_PRINCIPAL = "{\n" +
" \"users\": [\n" +
" {\n" +
" \"email\": \"GATEWAYSVCDESID\",\n" +
" \"role\": \"OWNER\"\n" +
" }\n" +
" ],\n" +
" \"ownersOf\": [\n" +
" {\n" +
" \"groupName\": \"users.datalake.viewers\"\n" +
" },\n" +
" {\n" +
" \"groupName\": \"users\"\n" +
" }\n" +
" ]\n" +
"}";
private final String DATAFIEREMAIL = "{\n" +
" \"users\": [\n" +
" {\n" +
" \"email\": \"DATAFIEREMAIL\",\n" +
" \"email\": \"SERVICE_PRINCIPAL\",\n" +
" \"role\": \"OWNER\"\n" +
" }\n" +
" ],\n" +
......@@ -71,25 +54,9 @@ public class KeySvcAccBeanConfigurationTests {
" ]\n" +
"}";
private final String SLISVCEMAIL = "{\n" +
" \"users\": [\n" +
" {\n" +
" \"email\": \"SLISVCEMAIL\",\n" +
" \"role\": \"OWNER\"\n" +
" }\n" +
" ],\n" +
" \"ownersOf\": [\n" +
" {\n" +
" \"groupName\": \"users.datalake.viewers\"\n" +
" },\n" +
" {\n" +
" \"groupName\": \"users\"\n" +
" }\n" +
" ]\n" +
"}";
@Before
public void setup() throws Exception {
when(appProperties.getGroupsOfServicePrincipal()).thenReturn("groups_of_service_principal.json");
prepareFileReaderForUsersTesting();
tenantInfo = Mockito.mock(TenantInfo.class);
when(requestInfo.getTenantInfo()).thenReturn(tenantInfo);
......@@ -97,16 +64,14 @@ public class KeySvcAccBeanConfigurationTests {
}
@Test
public void shouldReturnTrue_IfDatafierSvcAccount() throws Exception {
when(appProperties.getProjectId()).thenReturn("service-project-id");
public void shouldReturnTrue_IfServicePrincipalAccount() {
when(tenantInfo.getServiceAccount()).thenReturn("datafier@xxx.iam.gserviceaccount.com");
boolean res = sut.isKeyServiceAccount("datafier@xxx.iam.gserviceaccount.com");
assertTrue(res);
}
@Test
public void shouldReturnFalse_IfDNonKeySvcAccount() throws Exception {
when(appProperties.getProjectId()).thenReturn("service-project-id");
public void shouldReturnFalse_IfDNonKeySvcAccount() {
when(tenantInfo.getServiceAccount()).thenReturn("datafier@xxx.iam.gserviceaccount.com");
boolean res = sut.isKeyServiceAccount("member@xxx.com");
assertFalse(res);
......@@ -114,7 +79,6 @@ public class KeySvcAccBeanConfigurationTests {
@Test
public void shouldReturnGroups_ifGivenDatafierAcc() {
when(appProperties.getProjectId()).thenReturn("service-project-id");
when(tenantInfo.getServiceAccount()).thenReturn("datafier@xxx.iam.gserviceaccount.com");
Set<String> res = sut.getServiceAccountGroups("datafier@xxx.iam.gserviceaccount.com");
assertEquals(2, res.size());
......@@ -124,7 +88,6 @@ public class KeySvcAccBeanConfigurationTests {
@Test
public void shouldReturnEmptyGroupSet_ifGivenEmailIsNotKeySvcAcc() {
when(appProperties.getProjectId()).thenReturn("service-project-id");
when(tenantInfo.getServiceAccount()).thenReturn("datafier@xxx.iam.gserviceaccount.com");
Set<String> res = sut.getServiceAccountGroups("member@xxx.com");
assertEquals(0, res.size());
......@@ -132,7 +95,6 @@ public class KeySvcAccBeanConfigurationTests {
@Test
public void shouldReturnTrue_ifKeySvcAccInBootstrapGroup() {
when(appProperties.getProjectId()).thenReturn("service-project-id");
when(tenantInfo.getServiceAccount()).thenReturn("datafier@xxx.iam.gserviceaccount.com");
boolean res = sut.isKeySvcAccountInBootstrapGroup("users.data.root", "datafier@xxx.iam.gserviceaccount.com");
assertTrue(res);
......@@ -140,7 +102,6 @@ public class KeySvcAccBeanConfigurationTests {
@Test
public void shouldReturnFalse_ifGivenNonKeySvcAccInBootstrapGroup() {
when(appProperties.getProjectId()).thenReturn("service-project-id");
when(tenantInfo.getServiceAccount()).thenReturn("datafier@xxx.iam.gserviceaccount.com");
boolean res = sut.isKeySvcAccountInBootstrapGroup("users.data.root", "member@xxx.iam.gserviceaccount.com");
assertFalse(res);
......@@ -148,16 +109,12 @@ public class KeySvcAccBeanConfigurationTests {
@Test
public void shouldReturnFalse_ifGivenKeySvcAccInNonBootstrapGroup() {
when(appProperties.getProjectId()).thenReturn("service-project-id");
when(tenantInfo.getServiceAccount()).thenReturn("datafier@xxx.iam.gserviceaccount.com");
boolean res = sut.isKeySvcAccountInBootstrapGroup("users.test", "service-project-id@appspot.gserviceaccount.com");
assertFalse(res);
}
private void prepareFileReaderForUsersTesting() {
when(fileReaderService.readFile("/provisioning/accounts/datalake_ops.json")).thenReturn(DATAFIEREMAIL);
when(fileReaderService.readFile("/provisioning/accounts/datalake_viewers.json")).thenReturn(SLISVCEMAIL);
when(fileReaderService.readFile("/provisioning/accounts/datalake_root.json")).thenReturn(DATAFIEREMAIL);
when(fileReaderService.readFile("/provisioning/accounts/apigateway_serviceaccount.json")).thenReturn(GATEWAYSVCDESID);
when(fileReaderService.readFile("groups_of_service_principal.json")).thenReturn(SERVICE_PRINCIPAL);