Commit e43165b9 authored by Rostislav Dublin (EPAM)'s avatar Rostislav Dublin (EPAM)
Browse files

Merge branch 'gcp-logging-enhancements' into 'master'

Logging Enhancements for GCP modules (GONRG-1735, GONRG-1782)

See merge request osdu/platform/security-and-compliance/entitlements-gcp-java!2
parents 025ec8f4 52745cab
Pipeline #37729 canceled with stages
in 7 seconds
......@@ -59,6 +59,18 @@ Each API is authorized by the following steps:
```
</details>
* **GET /entitlements/v1/users**. Retrieves all the members that are within the data partition provided in the _data-partition-id_ header. This API lists the direct members of the entitlements service with their list of groups and roles (excluding service accounts). **NB!** At the moment, this API endpoint only available in GCP-JDBC implementation of the Entitlements Service.
<details>
```
curl --request GET \
--url '/entitlements/v1/users' \
--header 'authorization: Bearer <JWT>' \
--header 'content-type: application/json' \
--header 'data-partition-id: osdu'
```
</details>
* **GET /entitlements/v1/groups/{group_email}/members**. Retrieves members that belong to the _group_email_ within the data partition provided in the _data-partition-id_ header. Sample _group_email_ value is `{name}@{data-partition-id}.{domain}.com`. The query parameter role can be specified to a filter group members by role of OWNER or MEMBER. In addition to authorization, the user or service extracted from JWT (email claim) in the _Authorization_ header is checked for membership within _group_email_ as OWNER or MEMBER. This API lists the direct members of the group (excluding hierarchical groups).
<details>
......@@ -150,6 +162,10 @@ Instructions for running the GCP integration tests can be found [here][CGP docum
The database used in this implementation is PostgreSQL 13.0. The database structure and additional
info can be found [here][JDBC documentation].
### Integration test
You can use the same instructions for running the GCP integration tests to launch GCP-JDBC integration tests.
### Persistence Layer
The GCP implementation contains three mutually exclusive modules to work with the persistence layer.
......
......@@ -117,6 +117,12 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback.contrib</groupId>
<artifactId>logback-json-classic</artifactId>
<version>0.1.5</version>
</dependency>
<!-- OpenAPI -->
<dependency>
<groupId>org.springdoc</groupId>
......
......@@ -17,6 +17,7 @@
package org.opengroup.osdu.java.entitlements.api;
import java.util.Collection;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.opengroup.osdu.core.common.model.entitlements.CreateGroup;
......@@ -72,6 +73,13 @@ public class EntitlementsApi {
return new ResponseEntity<>(members, HttpStatus.OK);
}
@GetMapping("/users")
@PreAuthorize("@authorizationFilter.hasRole('" + EntitlementsRole.ADMIN +"')")
public ResponseEntity<Collection> listAllMembers(){
Collection members = entitlementsService.listAllMembers();
return new ResponseEntity<>(members, HttpStatus.OK);
}
@PostMapping("/groups/{groupEmail}/members")
@PreAuthorize("@authorizationFilter.hasRole('" + EntitlementsRole.USER + "')")
public ResponseEntity<MemberInfo> postMembers(
......
......@@ -15,12 +15,16 @@
* limitations under the License.
*/
package org.opengroup.osdu.java.entitlements.reference.conf;
package org.opengroup.osdu.java.entitlements.config;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
......@@ -32,4 +36,21 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.httpBasic().disable()
.csrf().disable(); //disable default authN. AuthN handled by endpoints proxy
}
}
\ No newline at end of file
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
}
// TODO temporary fix, should be removed later
@Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true);
firewall.setAllowUrlEncodedDoubleSlash(true);
return firewall;
}
}
......@@ -17,6 +17,7 @@
package org.opengroup.osdu.java.entitlements.service;
import java.util.Collection;
import javax.annotation.Nullable;
import org.opengroup.osdu.core.common.model.entitlements.CreateGroup;
import org.opengroup.osdu.core.common.model.entitlements.GroupEmail;
......@@ -35,6 +36,8 @@ public interface EntitlementsService {
Members getGroupMembers(GroupEmail groupEmail, @Nullable String cursor, @Nullable Integer limit,
@Nullable String role);
Collection listAllMembers();
Groups getMemberGroups();
GroupInfo createGroup(CreateGroup createGroup);
......
......@@ -17,6 +17,7 @@
package org.opengroup.osdu.java.entitlements.service;
import java.util.Collection;
import org.opengroup.osdu.core.common.model.entitlements.GroupEmail;
import org.opengroup.osdu.core.common.model.entitlements.MemberInfo;
......@@ -37,4 +38,6 @@ public interface GroupsRepository<GT, MT, GT1, MT1> {
GT1 listUserGroups(String memberEmail, String domain);
MT1 getGroupMembers(String groupEmail, String cursor, Integer limit, String role);
Collection getAllMembers();
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProfile name="local">
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%yellow([%thread]) %highlight(| %-5level |) %green(%d) %cyan(| %logger{15} |) %highlight(%msg) %n</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<springProfile name="!local">
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
<timestampFormat>yyyy-MM-dd HH:mm:ss.SSS</timestampFormat>
<timestampFormatTimezoneId>Etc/UTC</timestampFormatTimezoneId>
<appendLineSeparator>true</appendLineSeparator>
<jsonFormatter class="org.opengroup.osdu.core.gcp.logging.formatter.GoogleJsonFormatter">
<prettyPrint>false</prettyPrint>
</jsonFormatter>
</layout>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="stdout"/>
</root>
</springProfile>
</configuration>
......@@ -9,6 +9,7 @@ import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.TEST_GR
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.TEST_GROUP_NAME;
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.TEST_USER_EMAIL;
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.TEST_USER_ID;
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.getTestCollection;
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.getTestCreateGroupRequest;
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.getTestGroup;
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.getTestGroupEmail;
......@@ -16,6 +17,7 @@ import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.getTest
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.getTestMemberInfo;
import static org.opengroup.osdu.java.entitlements.util.TestDataProvider.getTestMembers;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
......@@ -101,6 +103,24 @@ public class EntitlementsApiTest {
});
}
@Test
public void should_returnMembers_whenListAllMembersCalled() throws Exception {
//given
MemberInfo testMember = getTestMemberInfo();
given(entitlementsService.listAllMembers())
.willReturn(getTestCollection(testMember));
//when
ResponseEntity<Collection> response = sut.listAllMembers();
//then
then(response.getStatusCode()).isEqualTo(HttpStatus.OK);
then(response.getBody()).isNotNull();
then(response.getBody().size()).isEqualTo(1);
then(response.getBody()).contains(testMember);
}
@Test
public void should_returnMemberInfo_whenValidMemberAndGroupProvided() throws Exception {
//given
......
package org.opengroup.osdu.java.entitlements.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import org.opengroup.osdu.core.common.model.entitlements.CreateGroup;
import org.opengroup.osdu.core.common.model.entitlements.GroupEmail;
import org.opengroup.osdu.core.common.model.entitlements.GroupInfo;
......@@ -60,4 +62,8 @@ public class TestDataProvider {
return members;
}
public static Collection getTestCollection(Object... objects){
return new ArrayList(Arrays.asList(objects));
}
}
# Use the official AdoptOpenJDK for a base image.
# https://hub.docker.com/_/openjdk
FROM openjdk:8-slim
WORKDIR /app
ARG PROVIDER_NAME
ENV PROVIDER_NAME $PROVIDER_NAME
ARG PORT
ENV PORT $PORT
# Copy the jar to the production image from the builder stage.
COPY provider/entitlements-${PROVIDER_NAME}/target/entitlements-${PROVIDER_NAME}-*-SNAPSHOT.jar entitlements-sql-${PROVIDER_NAME}.jar
# Run the web service on container startup.
CMD java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${PORT} -jar /app/entitlements-sql-${PROVIDER_NAME}.jar
# Copyright 2020 Google LLC
# Copyright 2017-2019, Schlumberger
# Copyright 2020 EPAM
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
steps:
- name: 'gcr.io/cloud-builders/docker'
args: [
'build',
'--build-arg', 'PROVIDER_NAME=${_PROVIDER_NAME}',
'--build-arg', 'PORT=${_PORT}',
'-t', 'gcr.io/$PROJECT_ID/${_APPLICATION_NAME}/${_GCP_SERVICE}-${_PROVIDER_NAME}:${_SHORT_SHA}',
'-t', 'gcr.io/$PROJECT_ID/${_APPLICATION_NAME}/${_GCP_SERVICE}-${_PROVIDER_NAME}:latest',
'-f', 'provider/${_GCP_SERVICE}-${_PROVIDER_NAME}/cloudbuild/Dockerfile.cloudbuild',
'.'
]
images:
- 'gcr.io/$PROJECT_ID/${_APPLICATION_NAME}/${_GCP_SERVICE}-${_PROVIDER_NAME}'
......@@ -38,6 +38,12 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.opengroup.osdu</groupId>
<artifactId>core-lib-gcp</artifactId>
<version>0.6.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.opengroup.osdu.java</groupId>
<artifactId>entitlements-core</artifactId>
......
......@@ -20,9 +20,18 @@ package org.opengroup.osdu.java.entitlements.jdbc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
@SpringBootApplication
@ComponentScan(basePackages = "org.opengroup.osdu")
@ComponentScan(
basePackages = "org.opengroup.osdu",
excludeFilters =
@Filter(
type = FilterType.REGEX,
pattern = {"org.opengroup.osdu.core.gcp.multitenancy.*"}
)
)
public class EntitlementsGcpJdbcApplication {
......
/*
* Copyright 2020 Google LLC
* Copyright 2020 EPAM Systems, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opengroup.osdu.java.entitlements.jdbc.config;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JdbcSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.httpBasic().disable()
.csrf().disable(); //disable default authN. AuthN handled by endpoints proxy
}
}
......@@ -30,9 +30,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
@Primary
@Component
@RequestScope
public class TenantFactoryImpl implements ITenantFactory {
private static final Logger LOG = LoggerFactory.getLogger(TenantFactoryImpl.class);
......@@ -69,6 +71,7 @@ public class TenantFactoryImpl implements ITenantFactory {
}
public void flushCache() {
// Cache is disabled for JDBC implementation
}
private void initTenants() {
......@@ -97,7 +100,7 @@ public class TenantFactoryImpl implements ITenantFactory {
});
this.tenants = new HashMap<>();
if (results.isEmpty()) {
if (results.isEmpty() && LOG.isErrorEnabled()) {
LOG.error(String.format("Collection \'%s\' is empty.", results));
}
for (TenantInfo tenantInfo : results) {
......
/*
Copyright 2020-2021 Google LLC
Copyright 2020-2021 EPAM Systems, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.opengroup.osdu.java.entitlements.jdbc.mapper;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.BooleanUtils.negate;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.opengroup.osdu.java.entitlements.jdbc.model.GroupRole;
import org.opengroup.osdu.java.entitlements.jdbc.model.MemberGroups;
import org.springframework.jdbc.core.ResultSetExtractor;
public class MemberGroupsExtractor implements ResultSetExtractor<Collection<MemberGroups>> {
@Override
public Collection<MemberGroups> extractData(ResultSet rs) throws SQLException {
Map<String, MemberGroups> data = new LinkedHashMap<>();
while (rs.next()){
String memberEmail = rs.getString("member_email");
String groupEmail = rs.getString("group_email");
String role = rs.getString("role");
data.putIfAbsent(memberEmail, MemberGroups.builder()
.email(memberEmail)
.groupRoles(new ArrayList<>())
.build());
if (negate(isNull(groupEmail)) && negate(isNull(role))){
data.get(memberEmail).getGroupRoles()
.add(GroupRole.builder()
.email(groupEmail)
.role(role)
.build()
);
}
}
return data.values();
}
}
/*
Copyright 2020-2021 Google LLC
Copyright 2020-2021 EPAM Systems, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.opengroup.osdu.java.entitlements.jdbc.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GroupRole {
private String email;
private String role;
}
/*
Copyright 2020-2021 Google LLC
Copyright 2020-2021 EPAM Systems, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.opengroup.osdu.java.entitlements.jdbc.model;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MemberGroups {
private String email;
private List<GroupRole> groupRoles;
}
......@@ -19,10 +19,10 @@ package org.opengroup.osdu.java.entitlements.jdbc.service;
import static java.util.Arrays.asList;
import java.util.Collection;
import java.util.Collections;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import org.opengroup.osdu.core.common.model.entitlements.CreateGroup;
import org.opengroup.osdu.core.common.model.entitlements.GroupEmail;
import org.opengroup.osdu.core.common.model.entitlements.GroupInfo;
......@@ -38,40 +38,32 @@ import org.opengroup.osdu.java.entitlements.logging.AuditLogger;
import org.opengroup.osdu.java.entitlements.model.MemberIdentity;
import org.opengroup.osdu.java.entitlements.service.EntitlementsService;
import org.opengroup.osdu.java.entitlements.service.GroupsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
@Log
public class JdbcEntitlementsServiceImpl implements EntitlementsService {
private static final String GROUP_NAME_REASON = "Invalid data group name";
private static final String GROUP_NAME_ERROR_MSG = "Prefix data mismatch";
@Autowired
private JdbcConfigurationProperties properties;
private final JdbcConfigurationProperties properties;
@Autowired
private DpsHeaders headers;
private final DpsHeaders headers;
@Autowired
private GroupsRepository<GroupInfo, MemberInfo, Groups, Members> groupsRepository;
private final GroupsRepository<GroupInfo, MemberInfo, Groups, Members> groupsRepository;
@Autowired
private MemberIdentity memberIdentity;
private final MemberIdentity memberIdentity;
@Autowired
private ITenantFactory tenantFactory;
private final ITenantFactory tenantFactory;
@Autowired
private AuditLogger auditLogger;
private final AuditLogger auditLogger;
@Override
public MemberInfo addMemberToGroup(GroupEmail groupEmail, MemberInfo memberInfo) {
checkTenant(headers.getPartitionId());
if (groupEmail.getGroupEmail().equals(memberInfo.getEmail())){
if (groupEmail.getGroupEmail().equals(memberInfo.getEmail())) {