diff --git a/indexer-service-azure/pom.xml b/indexer-service-azure/pom.xml index 7f76c0063895fa7be114f3281297d4190ed7c239..438bda1b1732fb17716340afa2ca0d30b06a7fcb 100644 --- a/indexer-service-azure/pom.xml +++ b/indexer-service-azure/pom.xml @@ -16,24 +16,38 @@ <description>Indexer Service Azure</description> <packaging>jar</packaging> - <dependencies> - <dependency> - <groupId>org.opendes.core</groupId> - <artifactId>indexer-search-core-azure</artifactId> - <version>0.0.1-SNAPSHOT</version> - <exclusions> - <exclusion> - <groupId>org.opendes.core</groupId> - <artifactId>indexer-search-core-root</artifactId> - </exclusion> - </exclusions> - </dependency> + <properties> + <azure.version>2.1.7</azure.version> + </properties> + <dependencies> <dependency> <groupId>org.opendes.indexer</groupId> <artifactId>indexer-service-root</artifactId> <version>1.0-SNAPSHOT</version> </dependency> + + <dependency> + <groupId>com.microsoft.azure</groupId> + <artifactId>azure-active-directory-spring-boot-starter</artifactId> + <version>${azure.version}</version> + </dependency> + <dependency> + <groupId>com.microsoft.azure</groupId> + <artifactId>azure-cosmosdb-spring-boot-starter</artifactId> + <version>${azure.version}</version> + </dependency> + <dependency> + <groupId>com.microsoft.azure</groupId > + <artifactId>azure-storage-spring-boot-starter</artifactId> + <version>${azure.version}</version> + </dependency> + <dependency> + <groupId>com.microsoft.azure</groupId> + <artifactId>azure-servicebus-spring-boot-starter</artifactId> + <version>${azure.version}</version> + </dependency> + </dependencies> <build> diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsClientFactory.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsClientFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..fea892cb0cd8b19c5a147a685eae16693694fab2 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsClientFactory.java @@ -0,0 +1,21 @@ +package org.opendes.indexer.azure.di; + +import org.opendes.client.api.entitlements.IEntitlementsFactory; +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@Component("AzureEntitlementsClientFactory") +@Primary +public class EntitlementsClientFactory extends AbstractFactoryBean<IEntitlementsFactory> { + + @Override + protected IEntitlementsFactory createInstance() throws Exception { + return new EntitlementsFactoryAzure(); + } + + @Override + public Class<?> getObjectType() { + return IEntitlementsFactory.class; + } +} diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsFactoryAzure.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsFactoryAzure.java new file mode 100644 index 0000000000000000000000000000000000000000..b4dc544dfcffbae670d1f5deada5ffcfe98b1ccf --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsFactoryAzure.java @@ -0,0 +1,12 @@ +package org.opendes.indexer.azure.di; + +import org.opendes.client.api.DpsHeaders; +import org.opendes.client.api.entitlements.IEntitlementsFactory; +import org.opendes.client.api.entitlements.IEntitlementsService; + +public class EntitlementsFactoryAzure implements IEntitlementsFactory { + @Override + public IEntitlementsService create(DpsHeaders headers) { + return new EntitlementsServiceAzure(headers); + } +} diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsServiceAzure.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsServiceAzure.java new file mode 100644 index 0000000000000000000000000000000000000000..d805136b92d7f61bfa6a2c20058596772d576d85 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/EntitlementsServiceAzure.java @@ -0,0 +1,87 @@ +package org.opendes.indexer.azure.di; + +import com.microsoft.azure.spring.autoconfigure.aad.UserPrincipal; +import org.apache.http.HttpStatus; +import org.opendes.client.api.DpsHeaders; +import org.opendes.client.api.entitlements.EntitlementsException; +import org.opendes.client.api.entitlements.IEntitlementsService; +import org.opendes.client.api.entitlements.models.*; +import org.opendes.client.httpclient.HttpResponse; +import org.opendes.core.model.SearchServiceRole; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class EntitlementsServiceAzure implements IEntitlementsService { + DpsHeaders headers; + + public EntitlementsServiceAzure(DpsHeaders headers){ + this.headers = headers; + } + + @Override + public MemberInfo addMember(GroupEmail groupEmail, MemberInfo memberInfo) throws EntitlementsException { + return null; + } + + @Override + public Members getMembers(GroupEmail groupEmail, GetMembers getMembers) throws EntitlementsException { + return null; + } + + @Override + public Groups getGroups() throws EntitlementsException { + final Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + final UserPrincipal current = (UserPrincipal) auth.getPrincipal(); + String email = String.valueOf(current.getUpn()); + + List<GroupInfo> giList = new ArrayList(); + Collection<? extends GrantedAuthority> authorities = auth.getAuthorities(); + for(GrantedAuthority authority : authorities) + { + GroupInfo gi = new GroupInfo(); + String role = authority.getAuthority(); + if (role.startsWith(SearchServiceRole.PREFIX)){ + role = role.substring(SearchServiceRole.PREFIX.length()); + } + gi.setName(role); + gi.setEmail(email); + giList.add(gi); + } + if (giList.size() > 0) + { + Groups groups = new Groups(); + groups.setGroups(giList); + groups.setDesId(email); + return groups; + } + + HttpResponse response = new HttpResponse(); + response.setResponseCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); + throw new EntitlementsException("no authorities found", response); + } + + @Override + public GroupInfo createGroup(CreateGroup createGroup) throws EntitlementsException { + return null; + } + + @Override + public void deleteMember(String s, String s1) throws EntitlementsException { + + } + + @Override + public Groups authorizeAny(String... strings) throws EntitlementsException { + return null; + } + + @Override + public void authenticate() throws EntitlementsException { + + } +} diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/TenantFactoryImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/TenantFactoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..ef4233b70eee64d26893736b28423966e11d619b --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/di/TenantFactoryImpl.java @@ -0,0 +1,45 @@ +package org.opendes.indexer.azure.di; + +import org.opendes.client.cache.ICache; +import org.opendes.client.multitenancy.ITenantFactory; +import org.opendes.client.multitenancy.TenantInfo; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Component +public class TenantFactoryImpl implements ITenantFactory { + public static final String DefaultTenantName = "common"; + private List<TenantInfo> tenants; + + public TenantFactoryImpl() + { + TenantInfo ti = new TenantInfo(); + ti.setName(DefaultTenantName); + this.tenants = new ArrayList<>(); + this.tenants.add(ti); + } + + public boolean exists(String tenantName) + { + return true; + } + + public TenantInfo getTenantInfo(String tenantName) { + // we are not checking tenantName yet, we have only 1 tenant + return this.tenants.get(0); + } + + public Collection<TenantInfo> listTenantInfo() { + return this.tenants; + } + + public <V> ICache<String, V> createCache(String tenantName, String host, int port, int expireTimeSeconds, Class<V> classOfV) + { + return null; + } + + public void flushCache() {} +} diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/kms/KmsClientImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/kms/KmsClientImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..8e1b169e602d7595cad7f9e5b7bda4a411381e22 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/kms/KmsClientImpl.java @@ -0,0 +1,17 @@ +package org.opendes.indexer.azure.kms; + +import org.opendes.core.kms.IKmsClient; + +import java.io.IOException; + +public class KmsClientImpl implements IKmsClient { + @Override + public String encryptString(String textToBeEncrypted) throws IOException { + return textToBeEncrypted; + } + + @Override + public String decryptString(String textToBeDecrypted) throws IOException { + return textToBeDecrypted; + } +} diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/ISchemaRepository.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/ISchemaRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..5f2d7dd978c701e91c59a4ede9818c7f10195c0c --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/ISchemaRepository.java @@ -0,0 +1,30 @@ + + +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.azure.model; + +public interface ISchemaRepository { + String SCHEMA_KIND = "IndexerSchema"; + + String SCHEMA = "schema"; + String USER = "user"; + String EXTENSION = "extension"; + + void add(Schema schema, String user); + + Schema get(String kind); +} + diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/Schema.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/Schema.java new file mode 100644 index 0000000000000000000000000000000000000000..14b1cf950ce8207aeb772c0dbf23f02836fae203 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/Schema.java @@ -0,0 +1,46 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.azure.model; + + import java.util.Map; + + import javax.validation.Valid; + import javax.validation.constraints.NotNull; + + import lombok.AllArgsConstructor; + import lombok.Data; + import lombok.NoArgsConstructor; + + import io.swagger.annotations.ApiModelProperty; + import org.opendes.indexer.SwaggerDoc; + import org.opendes.core.validation.ValidKind; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Schema { + + @ValidKind + @NotNull + @ApiModelProperty(value = SwaggerDoc.SCHEMA_REQUEST_KIND, + required = true, + example = SwaggerDoc.RECORD_KIND_EXAMPLE) + private String kind; + + @Valid + private SchemaItem[] schema; + + private Map<String, Object> ext; +} \ No newline at end of file diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/SchemaItem.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/SchemaItem.java new file mode 100644 index 0000000000000000000000000000000000000000..d4fe01a153d8b38733207a639a4d2bea0754d503 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/model/SchemaItem.java @@ -0,0 +1,41 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.azure.model; + +import java.util.Map; + +import javax.validation.constraints.NotEmpty; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SchemaItem { + + @NotEmpty + private String path; + + @NotEmpty + private String kind; + + @JsonInclude(value = Include.NON_NULL) + private Map<String, Object> ext; +} diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/ElasticRepositoryCosmosDB.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/ElasticRepositoryCosmosDB.java new file mode 100644 index 0000000000000000000000000000000000000000..b5796c629c3eef1dbbff4ece7e914ebe5520eaf3 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/ElasticRepositoryCosmosDB.java @@ -0,0 +1,12 @@ +package org.opendes.indexer.azure.persistence; + +import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.core.model.ClusterSettings; +import org.opendes.core.persistence.ElasticRepository; + +public class ElasticRepositoryCosmosDB implements ElasticRepository { + @Override + public ClusterSettings getElasticClusterSettings(TenantInfo tenantInfo) { + return null; + } +} diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/SchemaRepositoryImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/SchemaRepositoryImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a0a6445e608ff097a0de1dc000b8470d2ba910b1 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/persistence/SchemaRepositoryImpl.java @@ -0,0 +1,63 @@ +package org.opendes.indexer.azure.persistence; + +import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document; +import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey; +import com.microsoft.azure.spring.data.cosmosdb.repository.DocumentDbRepository; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.opendes.indexer.azure.model.Schema; +import org.opendes.indexer.azure.model.SchemaItem; +import org.opendes.indexer.azure.model.ISchemaRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.Id; +import org.springframework.stereotype.Repository; + +import java.util.Map; +import java.util.Optional; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Document(collection = "StorageSchema") //collection name +class SchemaDoc { + + @PartitionKey + @Id + private String kind; + private Map<String,Object> extension; + private String user; + private SchemaItem[] schemaItems; +} + +interface CosmosDB extends DocumentDbRepository<SchemaDoc, String> {} + +@Repository +public class SchemaRepositoryImpl implements ISchemaRepository { + + @Autowired + private CosmosDB db; + + @Override + public void add(Schema schema, String user) { + SchemaDoc sd = new SchemaDoc(); + sd.setKind(schema.getKind()); + sd.setExtension(schema.getExt()); + sd.setUser(user); + sd.setSchemaItems(schema.getSchema()); + db.save(sd); + } + + @Override + public Schema get(String kind) { + Optional<SchemaDoc> sd = db.findById(kind); + if (!sd.isPresent()) + return null; + + Schema newSchema = new Schema(); + newSchema.setKind(kind); + newSchema.setSchema(sd.get().getSchemaItems()); + newSchema.setExt(sd.get().getExtension()); + return newSchema; + } +} diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/RequestInfoImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/RequestInfoImpl.java index eccd28d599acbcf4d2b697a25d290a5e36d1532b..a1210a82345698a0ac231913a467adab195ed8fe 100644 --- a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/RequestInfoImpl.java +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/RequestInfoImpl.java @@ -1,11 +1,13 @@ package org.opendes.indexer.azure.util; import com.google.common.base.Strings; -import com.slb.core.model.AppEngineHeaders; -import com.slb.core.model.DeploymentEnvironment; -import com.slb.core.util.HeadersInfo; import org.apache.http.HttpStatus; import org.opendes.client.api.DpsHeaders; +import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.core.model.DeploymentEnvironment; +import org.opendes.core.util.AppException; +import org.opendes.core.util.Config; +import org.opendes.core.util.HeadersInfo; import org.opendes.indexer.util.IRequestInfo; import org.springframework.beans.factory.annotation.Autowired; @@ -22,9 +24,11 @@ public class RequestInfoImpl implements IRequestInfo { private static final String expectedCronHeaderValue = "true"; + @Autowired + private TenantInfo tenantInfo; + @Override public DpsHeaders getHeaders() { - return this.headersInfo.getHeaders(); } @@ -51,18 +55,10 @@ public class RequestInfoImpl implements IRequestInfo { } @Override - public boolean isCronRequest() { - String appEngineCronHeader = this.headersInfo.getHeadersMap().getOrDefault(AppEngineHeaders.CRON_SERVICE, null); - return expectedCronHeaderValue.equalsIgnoreCase(appEngineCronHeader); - } + public boolean isCronRequest() { return false; } @Override - public boolean isTaskQueueRequest() { - if (!this.headersInfo.getHeadersMap().containsKey(AppEngineHeaders.TASK_QUEUE_NAME)) return false; - - String queueId = this.headersInfo.getHeadersMap().get(AppEngineHeaders.TASK_QUEUE_NAME); - return queueId.endsWith(Constants.INDEXER_QUEUE_IDENTIFIER); - } + public boolean isTaskQueueRequest() { return false; } private String checkOrGetAuthorizationHeader() { if (Config.getDeploymentEnvironment() == DeploymentEnvironment.LOCAL) { @@ -72,7 +68,8 @@ public class RequestInfoImpl implements IRequestInfo { } return authHeader; } else { - return "Bearer " + this.serviceAccountJwtClient.getIdToken(); + return "Bearer " + this.serviceAccountJwtClient.getIdToken(tenantInfo.getName()); } } + } diff --git a/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..609a5725594a463613222b4037ef62fff1915a12 --- /dev/null +++ b/indexer-service-azure/src/main/java/org/opendes/indexer/azure/util/ServiceAccountJwtClientImpl.java @@ -0,0 +1,12 @@ +package org.opendes.indexer.azure.util; + +import org.opendes.core.util.IServiceAccountJwtClient; +import org.springframework.stereotype.Component; + +@Component +public class ServiceAccountJwtClientImpl implements IServiceAccountJwtClient { + @Override + public String getIdToken(String tenantName){ + return "common"; + } +} diff --git a/indexer-service-gcp/pom.xml b/indexer-service-gcp/pom.xml index b95598f18a3be4fd84ae9f39a6ed6d570f2854f1..87f79f80e85cf9ad3475252a1c2988121ab6f0f3 100644 --- a/indexer-service-gcp/pom.xml +++ b/indexer-service-gcp/pom.xml @@ -18,49 +18,93 @@ <dependencies> <dependency> - <groupId>org.opendes.core</groupId> - <artifactId>indexer-search-core-gcp</artifactId> - <version>1.1-SNAPSHOT</version> + <groupId>org.opendes.indexer</groupId> + <artifactId>indexer-service-root</artifactId> + <version>1.0-SNAPSHOT</version> + </dependency> + + <dependency> + <groupId>org.os</groupId> + <artifactId>client-lib-gcp</artifactId> + <version>0.0.8</version> <exclusions> <exclusion> - <groupId>org.opendes.core</groupId> - <artifactId>indexer-search-core-root</artifactId> + <groupId>org.os</groupId> + <artifactId>client-lib</artifactId> </exclusion> </exclusions> </dependency> <dependency> - <groupId>org.opendes.indexer</groupId> - <artifactId>indexer-service-root</artifactId> - <version>1.0-SNAPSHOT</version> + <groupId>com.google.cloud</groupId> + <artifactId>google-cloud-datastore</artifactId> + <version>1.72.0</version> + </dependency> + <dependency> + <groupId>com.google.cloud</groupId> + <artifactId>google-cloud-logging</artifactId> + <version>1.72.0</version> + </dependency> + <dependency> + <groupId>com.google.apis</groupId> + <artifactId>google-api-services-storage</artifactId> + <version>v1-rev150-1.25.0</version> + <exclusions> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava-jdk5</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>com.google.apis</groupId> + <artifactId>google-api-services-cloudkms</artifactId> + <version>v1-rev81-1.25.0</version> + </dependency> + <!-- https://mvnrepository.com/artifact/com.google.cloud/google-cloud-pubsub --> + <dependency> + <groupId>com.google.cloud</groupId> + <artifactId>google-cloud-pubsub</artifactId> + <version>1.60.0</version> </dependency> - -<!-- <dependency>--> -<!-- <groupId>com.google.cloud</groupId>--> -<!-- <artifactId>google-cloud-datastore</artifactId>--> -<!-- <version>1.72.0</version>--> -<!-- </dependency>--> -<!-- <dependency>--> -<!-- <groupId>com.google.cloud</groupId>--> -<!-- <artifactId>google-cloud-logging</artifactId>--> -<!-- <version>1.72.0</version>--> -<!-- </dependency>--> <dependency> <groupId>com.google.appengine.tools</groupId> <artifactId>appengine-gcs-client</artifactId> <version>0.8</version> </dependency> - - <!-- <dependency>--> -<!-- <groupId>com.google.apis</groupId>--> -<!-- <artifactId>google-api-services-storage</artifactId>--> -<!-- <version>v1-rev150-1.25.0</version>--> -<!-- <exclusions>--> -<!-- <exclusion>--> -<!-- <groupId>com.google.guava</groupId>--> -<!-- <artifactId>guava-jdk5</artifactId>--> -<!-- </exclusion>--> -<!-- </exclusions>--> +<!-- <dependency>--> +<!-- <groupId>org.mockito</groupId>--> +<!-- <artifactId>mockito-core</artifactId>--> +<!-- <scope>test</scope>--> +<!-- </dependency>--> +<!-- <dependency>--> +<!-- <groupId>junit</groupId>--> +<!-- <artifactId>junit</artifactId>--> +<!-- <scope>test</scope>--> +<!-- </dependency>--> +<!-- <dependency>--> +<!-- <groupId>org.powermock</groupId>--> +<!-- <artifactId>powermock-core</artifactId>--> +<!-- <version>2.0.2</version>--> +<!-- <scope>test</scope>--> +<!-- </dependency>--> +<!-- <dependency>--> +<!-- <groupId>org.powermock</groupId>--> +<!-- <artifactId>powermock-api-mockito2</artifactId>--> +<!-- <version>2.0.2</version>--> +<!-- <scope>test</scope>--> +<!-- </dependency>--> +<!-- <dependency>--> +<!-- <groupId>org.springframework</groupId>--> +<!-- <artifactId>spring-test</artifactId>--> +<!-- <version>5.1.9.RELEASE</version>--> +<!-- <scope>test</scope>--> +<!-- </dependency>--> +<!-- <dependency>--> +<!-- <groupId>org.springframework</groupId>--> +<!-- <artifactId>spring-test</artifactId>--> +<!-- <version>5.1.9.RELEASE</version>--> +<!-- <scope>test</scope>--> <!-- </dependency>--> </dependencies> diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/IndexerGcpApplication.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/IndexerGcpApplication.java index 07ae5c446b998a9dc8b3e506dc5fdd487afefcd6..402e5c8a2a836b1b97b14a9752b316eb5955802a 100644 --- a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/IndexerGcpApplication.java +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/IndexerGcpApplication.java @@ -10,4 +10,4 @@ public class IndexerGcpApplication { SpringApplication.run(IndexerGcpApplication.class, args); } -} \ No newline at end of file +} diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/cache/DatastoreCredentialCache.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/cache/DatastoreCredentialCache.java new file mode 100644 index 0000000000000000000000000000000000000000..49f1b4399493d547d171ad6967c7e9d5c6aad354 --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/cache/DatastoreCredentialCache.java @@ -0,0 +1,27 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.cache; + +import com.google.auth.oauth2.AccessToken; +import org.opendes.client.cache.RedisCache; +import org.springframework.beans.factory.annotation.Value; + +public class DatastoreCredentialCache extends RedisCache<String, AccessToken> { + + // Datastore credentials are only valid for 1hr, release the key 2 minutes before the expiration + public DatastoreCredentialCache(@Value("${REDIS_SEARCH_HOST}") final String REDIS_SEARCH_HOST, @Value("${REDIS_SEARCH_PORT}") final String REDIS_SEARCH_PORT) { + super(REDIS_SEARCH_HOST, Integer.parseInt(REDIS_SEARCH_PORT), 58 * 60, String.class, AccessToken.class); + } +} \ No newline at end of file diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/kms/KmsClient.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/kms/KmsClient.java new file mode 100644 index 0000000000000000000000000000000000000000..a57b236760cbc8bff2c9f6cb9a46e431185312a1 --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/kms/KmsClient.java @@ -0,0 +1,96 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.kms; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.cloudkms.v1.CloudKMS; +import com.google.api.services.cloudkms.v1.CloudKMSScopes; +import com.google.api.services.cloudkms.v1.model.DecryptRequest; +import com.google.api.services.cloudkms.v1.model.DecryptResponse; +import com.google.api.services.cloudkms.v1.model.EncryptRequest; +import com.google.api.services.cloudkms.v1.model.EncryptResponse; +import org.opendes.core.kms.IKmsClient; +import org.opendes.core.util.Config; +import org.opendes.core.util.Preconditions; +import org.springframework.beans.factory.annotation.Value; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class KmsClient implements IKmsClient { + + @Value("${KMS_KEY}") + private String KMS_KEY; + + @Value("${KEY_RING}") + private String KEY_RING; + + private static final String KEY_NAME = "projects/%s/locations/global/keyRings/%s/cryptoKeys/%s"; + + /** + * Encrypts the given plaintext using the specified crypto key. + * Google KMS automatically uses the new primary key version to encrypt data, so this could be directly used for key rotation + */ + public String encryptString(String textToBeEncrypted) throws IOException { + Preconditions.checkNotNullOrEmpty(textToBeEncrypted, "textToBeEncrypted cannot be null"); + + byte[] plaintext = textToBeEncrypted.getBytes(StandardCharsets.UTF_8); + String resourceName = String.format(KEY_NAME, Config.getGoogleCloudProjectId(), KEY_RING, KMS_KEY); + CloudKMS kms = createAuthorizedClient(); + EncryptRequest request = new EncryptRequest().encodePlaintext(plaintext); + EncryptResponse response = kms.projects().locations().keyRings().cryptoKeys() + .encrypt(resourceName, request) + .execute(); + return response.getCiphertext(); + } + + /** + * Decrypts the provided ciphertext with the specified crypto key. + * Google KMS automatically uses the correct key version to decrypt data, as long as the key version is not disabled + */ + public String decryptString(String textToBeDecrypted) throws IOException { + Preconditions.checkNotNullOrEmpty(textToBeDecrypted, "textToBeDecrypted cannot be null"); + + CloudKMS kms = createAuthorizedClient(); + String cryptoKeyName = String.format(KEY_NAME, Config.getGoogleCloudProjectId(), KEY_RING, KMS_KEY); + DecryptRequest request = new DecryptRequest().setCiphertext(textToBeDecrypted); + DecryptResponse response = kms.projects().locations().keyRings().cryptoKeys() + .decrypt(cryptoKeyName, request) + .execute(); + return new String(response.decodePlaintext(), StandardCharsets.UTF_8).trim(); + } + + /** + * Creates an authorized CloudKMS client service using Application Default Credentials. + * + * @return an authorized CloudKMS client + * @throws IOException if there's an error getting the default credentials. + */ + private CloudKMS createAuthorizedClient() throws IOException { + HttpTransport transport = new NetHttpTransport(); + JsonFactory jsonFactory = new JacksonFactory(); + GoogleCredential credential = GoogleCredential.getApplicationDefault(); + if (credential.createScopedRequired()) { + credential = credential.createScoped(CloudKMSScopes.all()); + } + return new CloudKMS.Builder(transport, jsonFactory, credential) + .setApplicationName("CloudKMS snippets") + .build(); + } +} \ No newline at end of file diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/model/AppEngineHeaders.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/model/AppEngineHeaders.java new file mode 100644 index 0000000000000000000000000000000000000000..9c14a63a5fe1118bf7db9b18dba5dbb5e4e29d45 --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/model/AppEngineHeaders.java @@ -0,0 +1,28 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.model; + +public class AppEngineHeaders { + public static final String DATA_GROUPS = "X-Data-Groups"; + public static final String TASK_QUEUE_RETRY_COUNT = "X-AppEngine-TaskExecutionCount"; + public static final String TASK_QUEUE_NAME = "X-AppEngine-QueueName"; + public static final String CITY_LAT_LONG = "X-AppEngine-CityLatLong"; + public static final String COUNTRY = "X-AppEngine-Country"; + public static final String REGION = "X-AppEngine-Region"; + public static final String CITY = "X-AppEngine-City"; + public static final String CLOUD_TRACE_CONTEXT = "X-Cloud-Trace-Context"; + public static final String TRACE_ID = "X-Trace-Id"; + public static final String CRON_SERVICE = "X-AppEngine-Cron"; +} \ No newline at end of file diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreCredential.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreCredential.java new file mode 100644 index 0000000000000000000000000000000000000000..4a8d7284a7f9f461bd508e921aaec9c8cef53ae2 --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreCredential.java @@ -0,0 +1,109 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.persistence; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.iam.v1.Iam; +import com.google.api.services.iam.v1.Iam.Projects.ServiceAccounts.SignJwt; +import com.google.api.services.iam.v1.model.SignJwtRequest; +import com.google.api.services.iam.v1.model.SignJwtResponse; +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.gson.JsonObject; +import org.apache.commons.lang3.time.DateUtils; +import org.opendes.client.cryptographic.Crc32c; +import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.indexer.gcp.cache.DatastoreCredentialCache; + +import java.util.Date; + +public class DatastoreCredential extends GoogleCredentials { + + private static final long serialVersionUID = 8344377091688956815L; + private static final JsonFactory JSON_FACTORY = new JacksonFactory(); + private Iam iam; + + private final TenantInfo tenant; + private final DatastoreCredentialCache cache; + + protected DatastoreCredential(TenantInfo tenant, DatastoreCredentialCache cache) { + this.tenant = tenant; + this.cache = cache; + } + + @Override + public AccessToken refreshAccessToken() { + + String cacheKey = this.getCacheKey(); + + AccessToken accessToken = this.cache.get(cacheKey); + + if (accessToken != null) { + return accessToken; + } + + try { + SignJwtRequest signJwtRequest = new SignJwtRequest(); + signJwtRequest.setPayload(this.getPayload()); + + String serviceAccountName = String.format("projects/-/serviceAccounts/%s", this.tenant.getServiceAccount()); + + SignJwt signJwt = this.getIam().projects().serviceAccounts().signJwt(serviceAccountName, signJwtRequest); + + SignJwtResponse signJwtResponse = signJwt.execute(); + String signedJwt = signJwtResponse.getSignedJwt(); + + accessToken = new AccessToken(signedJwt, DateUtils.addSeconds(new Date(), 3600)); + + this.cache.put(cacheKey, accessToken); + + return accessToken; + } catch (Exception e) { + throw new RuntimeException("Error creating datastore credential", e); + } + } + + private String getPayload() { + JsonObject payload = new JsonObject(); + payload.addProperty("iss", this.tenant.getServiceAccount()); + payload.addProperty("sub", this.tenant.getServiceAccount()); + payload.addProperty("aud", "https://datastore.googleapis.com/google.datastore.v1.Datastore"); + payload.addProperty("iat", System.currentTimeMillis() / 1000); + + return payload.toString(); + } + + protected void setIam(Iam iam) { + this.iam = iam; + } + + private Iam getIam() throws Exception { + if (this.iam == null) { + + Iam.Builder builder = new Iam.Builder(GoogleNetHttpTransport.newTrustedTransport(), JSON_FACTORY, + GoogleCredential.getApplicationDefault()).setApplicationName("Search Service"); + + this.iam = builder.build(); + } + return this.iam; + } + + private String getCacheKey() { + return Crc32c.hashToBase64EncodedString(String.format("datastoreCredential:%s", this.tenant.getName())); + } +} \ No newline at end of file diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreFactory.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..5d55a6863bb48fd1f857d2f4b9c4c89741fe54c7 --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/DatastoreFactory.java @@ -0,0 +1,65 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.persistence; + +import com.google.api.gax.retrying.RetrySettings; +import com.google.cloud.TransportOptions; +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.DatastoreOptions; +import com.google.cloud.http.HttpTransportOptions; +import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.indexer.gcp.cache.DatastoreCredentialCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.threeten.bp.Duration; + +import java.util.HashMap; +import java.util.Map; + +public class DatastoreFactory { + + @Autowired + private DatastoreCredentialCache cache; + + private static Map<String, Datastore> DATASTORE_CLIENTS = new HashMap<>(); + + private static final RetrySettings RETRY_SETTINGS = RetrySettings.newBuilder() + .setMaxAttempts(6) + .setInitialRetryDelay(Duration.ofSeconds(10)) + .setMaxRetryDelay(Duration.ofSeconds(32)) + .setRetryDelayMultiplier(2.0) + .setTotalTimeout(Duration.ofSeconds(50)) + .setInitialRpcTimeout(Duration.ofSeconds(50)) + .setRpcTimeoutMultiplier(1.0) + .setMaxRpcTimeout(Duration.ofSeconds(50)) + .build(); + + private static final TransportOptions TRANSPORT_OPTIONS = HttpTransportOptions.newBuilder() + .setReadTimeout(30000) + .build(); + + public Datastore getDatastoreInstance(TenantInfo tenantInfo) { + if (DATASTORE_CLIENTS.get(tenantInfo.getName()) == null) { + Datastore googleDatastore = DatastoreOptions.newBuilder() + .setCredentials(new DatastoreCredential(tenantInfo, this.cache)) + .setRetrySettings(RETRY_SETTINGS) + .setTransportOptions(TRANSPORT_OPTIONS) + .setNamespace(tenantInfo.getName()) + .setProjectId(tenantInfo.getProjectId()) + .build().getService(); + DATASTORE_CLIENTS.put(tenantInfo.getName(), googleDatastore); + } + return DATASTORE_CLIENTS.get(tenantInfo.getName()); + } +} diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/ElasticRepositoryDatastore.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/ElasticRepositoryDatastore.java new file mode 100644 index 0000000000000000000000000000000000000000..fed47389b5613a2067d3fb9d5d243f082239be16 --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/persistence/ElasticRepositoryDatastore.java @@ -0,0 +1,81 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.persistence; + +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.Entity; +import com.google.cloud.datastore.Key; +import org.apache.http.HttpStatus; +import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.core.model.ClusterSettings; +import org.opendes.core.persistence.ElasticRepository; +import org.opendes.core.util.AppException; +import org.opendes.core.util.Config; +import org.opendes.core.util.Preconditions; +import org.opendes.indexer.gcp.kms.KmsClient; +import org.springframework.beans.factory.annotation.Autowired; + + +public class ElasticRepositoryDatastore implements ElasticRepository { + + static final String HOST = "host"; + static final String PORT = "port"; + static final String XPACK_RESTCLIENT_CONFIGURATION = "configuration"; + + @Autowired + private KmsClient kmsClient; + @Autowired + private DatastoreFactory datastoreFactory; + + @Override + public ClusterSettings getElasticClusterSettings(TenantInfo tenantInfo) { + + Datastore googleDatastore = this.datastoreFactory.getDatastoreInstance(tenantInfo); + Key key = googleDatastore.newKeyFactory().setKind(Config.getElasticCredentialsDatastoreKind()).newKey(Config.getElasticCredentialsDatastoreId()); + Entity datastoreEntity = googleDatastore.get(key); + + if (datastoreEntity == null) { + throw new AppException(HttpStatus.SC_NOT_FOUND, "Cluster setting not found", "The requested cluster setting was not found in datastore.", String.format("Cluster setting with key: '%s' does not exist in datastore.", key.getName())); + } + + String encryptedHost = null; + String encryptedPort = null; + String encryptedConfiguration = null; + + try { + encryptedHost = datastoreEntity.getString(HOST); + encryptedPort = datastoreEntity.getString(PORT); + encryptedConfiguration = datastoreEntity.getString(XPACK_RESTCLIENT_CONFIGURATION); + + String host = this.kmsClient.decryptString(encryptedHost); + String portString = this.kmsClient.decryptString(encryptedPort); + String usernameAndPassword = this.kmsClient.decryptString(encryptedConfiguration); + + Preconditions.checkNotNullOrEmpty(host, "host cannot be null"); + Preconditions.checkNotNullOrEmpty(portString, "port cannot be null"); + Preconditions.checkNotNullOrEmpty(usernameAndPassword, "configuration cannot be null"); + + int port = Integer.parseInt(portString); + + return new ClusterSettings(host, port, usernameAndPassword); + } catch (GoogleJsonResponseException e) { + String debuggingInfo = String.format("Host: %s | port: %s | configuration: %s", encryptedHost, encryptedPort, encryptedConfiguration); + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cluster setting decryption error", "An error has occurred decrypting cluster settings.", debuggingInfo, e); + } catch (Exception e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Cluster setting fetch error", "An error has occurred fetching cluster settings from the datastore.", e); + } + } +} \ No newline at end of file diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/publish/PublisherImpl.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/publish/PublisherImpl.java index c40adc97f93ec4dee716febc343f659cd72efa7c..67198fb068c0affdedeef181cec2c5a63cb18497 100644 --- a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/publish/PublisherImpl.java +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/publish/PublisherImpl.java @@ -23,16 +23,16 @@ import com.google.gson.reflect.TypeToken; import com.google.protobuf.ByteString; import com.google.pubsub.v1.ProjectTopicName; import com.google.pubsub.v1.PubsubMessage; -import com.slb.core.model.AppEngineHeaders; -import com.slb.core.model.DeploymentEnvironment; -import com.slb.core.util.AppException; -import com.slb.core.util.Config; import org.apache.http.HttpStatus; import org.elasticsearch.common.Strings; import org.opendes.client.api.DpsHeaders; import org.opendes.client.gcp.PubSub.PubSubExtensions; import org.opendes.client.multitenancy.ITenantFactory; import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.core.model.DeploymentEnvironment; +import org.opendes.core.util.AppException; +import org.opendes.core.util.Config; +import org.opendes.indexer.gcp.model.AppEngineHeaders; import org.opendes.indexer.model.RecordStatus; import org.opendes.indexer.publish.IPublisher; import org.opendes.indexer.util.JobStatus; @@ -59,9 +59,9 @@ public class PublisherImpl implements IPublisher { private PubSubExtensions pubSubExtensions; @Override - public PubsubMessage publishStatusChangedTagsToTopic(DpsHeaders headers, JobStatus indexerBatchStatus) throws Exception { + public void publishStatusChangedTagsToTopic(DpsHeaders headers, JobStatus indexerBatchStatus) throws Exception { - if (Config.getDeploymentEnvironment() == DeploymentEnvironment.LOCAL) return null; + if (Config.getDeploymentEnvironment() == DeploymentEnvironment.LOCAL); String tenant = headers.getPartitionId(); if(Strings.isNullOrEmpty(tenant)) @@ -74,7 +74,7 @@ public class PublisherImpl implements IPublisher { PubsubMessage pubsubMessage = getPubsubMessage(headers, indexerBatchStatus); pubSubExtensions.publishAndCreateTopicIfNotExist(publisher, pubsubMessage); - return pubsubMessage; + } private static final RetrySettings RETRY_SETTINGS = RetrySettings.newBuilder() @@ -99,13 +99,13 @@ public class PublisherImpl implements IPublisher { String tenant = headers.getPartitionId(); //This code it to provide backward compatibility to slb-account-id if(!Strings.isNullOrEmpty(tenant)) { - builder.putAttributes(DpsHeaders.DATA_PARITION_ID, headers.getPartitionId()); + builder.putAttributes(DpsHeaders.DATA_PARTITION_ID, headers.getPartitionId()); } else { builder.putAttributes(DpsHeaders.ACCOUNT_ID, headers.getAccountId()); } builder.putAttributes(DpsHeaders.CORRELATION_ID, headers.getCorrelationId()); - builder.putAttributes(AppEngineHeaders.CLOUD_TRACE_CONTEXT, headers.getHeaders().get(AppEngineHeaders.CLOUD_TRACE_CONTEXT)); + builder.putAttributes( AppEngineHeaders.CLOUD_TRACE_CONTEXT, headers.getHeaders().get(AppEngineHeaders.CLOUD_TRACE_CONTEXT)); builder.setData(statusChangedTagsData); return builder.build(); diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImpl.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4ed85ae2989a72be9cf5fd5a949a3cbf2715785c --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImpl.java @@ -0,0 +1,115 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.util; + +import com.google.common.base.Strings; +import org.opendes.client.api.DpsHeaders; +import org.opendes.core.model.SlbHeaders; +import org.opendes.core.util.IHeadersInfo; +import org.opendes.core.util.Preconditions; +import org.opendes.indexer.gcp.model.AppEngineHeaders; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; + +import java.util.HashSet; +import java.util.Map; +import java.util.stream.Collectors; + +public class HeadersInfoGcpImpl implements IHeadersInfo { + + @Autowired + private HttpHeaders httpHeaders; + + private DpsHeaders headersMap = null; + + private static final HashSet<String> FORBIDDEN_FROM_LOGGING = new HashSet<>(); + static { + FORBIDDEN_FROM_LOGGING.add(DpsHeaders.AUTHORIZATION); + FORBIDDEN_FROM_LOGGING.add(DpsHeaders.ON_BEHALF_OF); + } + + private static final HashSet<String> FORWARDED_HEADERS = new HashSet<>(); + static { + FORWARDED_HEADERS.add(AppEngineHeaders.CITY_LAT_LONG); + FORWARDED_HEADERS.add(AppEngineHeaders.COUNTRY); + FORWARDED_HEADERS.add(AppEngineHeaders.REGION); + FORWARDED_HEADERS.add(AppEngineHeaders.CITY); + FORWARDED_HEADERS.add(AppEngineHeaders.TASK_QUEUE_RETRY_COUNT); + FORWARDED_HEADERS.add(AppEngineHeaders.TASK_QUEUE_NAME); + FORWARDED_HEADERS.add(AppEngineHeaders.DATA_GROUPS); + FORWARDED_HEADERS.add(AppEngineHeaders.CLOUD_TRACE_CONTEXT); + FORWARDED_HEADERS.add(AppEngineHeaders.TRACE_ID); + FORWARDED_HEADERS.add(AppEngineHeaders.CRON_SERVICE); + FORWARDED_HEADERS.add(SlbHeaders.PRIMARY_PARTITION_ID); + } + + @Override + public DpsHeaders getHeaders() { + if (headersMap == null) { + headersMap = this.getCoreServiceHeaders(httpHeaders.toSingleValueMap()); + } + return headersMap; + } + + @Override + public String getUser() { + return getHeaders().getUserEmail(); + } + + @Override + public String getPartitionId() { + return getHeaders().getPartitionIdWithFallbackToAccountId(); + } + + @Override + public String getPrimaryPartitionId() { + return getHeadersMap().get(SlbHeaders.PRIMARY_PARTITION_ID); + } + + @Override + public Map<String, String> getHeadersMap() { + return getHeaders().getHeaders(); + } + + @Override + public DpsHeaders getCoreServiceHeaders(Map<String, String> input) { + Preconditions.checkNotNull(input, "input headers cannot be null"); + + DpsHeaders output = DpsHeaders.createFromMap(input); + input.forEach((key,value) -> { + if (FORWARDED_HEADERS.contains(key)) { + if (key.equals(AppEngineHeaders.CLOUD_TRACE_CONTEXT)) { + String traceContext = input.get(AppEngineHeaders.CLOUD_TRACE_CONTEXT); + if (!Strings.isNullOrEmpty(traceContext)) { + output.put(AppEngineHeaders.TRACE_ID, TraceIdExtractor.getTraceId(traceContext)); + output.put(key, traceContext); + } + } else { + if (!output.getHeaders().containsKey(key.toLowerCase())) { + output.put(key, input.get(key)); + } + } + } + }); + output.addCorrelationIdIfMissing(); + return output; + } + + @Override + public String toString() { + return this.getHeadersMap().entrySet().stream().filter(map -> !FORBIDDEN_FROM_LOGGING.contains(map.getKey().toLowerCase())).map(Map.Entry::toString).collect(Collectors.joining(" | ")); + } + +} \ No newline at end of file diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/RequestInfoImpl.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/RequestInfoImpl.java index 0a645c8f26b0c332db80d14caa21d29a55498c12..edc5f15bb1f055005fdeef772fe62122e44f3129 100644 --- a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/RequestInfoImpl.java +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/RequestInfoImpl.java @@ -1,11 +1,15 @@ package org.opendes.indexer.gcp.util; import com.google.common.base.Strings; -import com.slb.core.model.AppEngineHeaders; -import com.slb.core.model.DeploymentEnvironment; -import com.slb.core.util.*; import org.apache.http.HttpStatus; import org.opendes.client.api.DpsHeaders; +import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.core.model.DeploymentEnvironment; +import org.opendes.core.util.AppException; +import org.opendes.core.util.Config; +import org.opendes.core.util.Constants; +import org.opendes.core.util.HeadersInfo; +import org.opendes.indexer.gcp.model.AppEngineHeaders; import org.opendes.indexer.util.IRequestInfo; import org.springframework.beans.factory.annotation.Autowired; @@ -18,7 +22,10 @@ public class RequestInfoImpl implements IRequestInfo { @Autowired private HeadersInfo headersInfo; @Autowired - private ServiceAccountJwtClient serviceAccountJwtClient; + private ServiceAccountJwtGcpClientImpl serviceAccountJwtClient; + + @Autowired + private TenantInfo tenantInfo; private static final String expectedCronHeaderValue = "true"; @@ -64,7 +71,7 @@ public class RequestInfoImpl implements IRequestInfo { return queueId.endsWith(Constants.INDEXER_QUEUE_IDENTIFIER); } - private String checkOrGetAuthorizationHeader() { + public String checkOrGetAuthorizationHeader() { if (Config.getDeploymentEnvironment() == DeploymentEnvironment.LOCAL) { String authHeader = this.headersInfo.getHeaders().getAuthorization(); if (Strings.isNullOrEmpty(authHeader)) { @@ -72,7 +79,7 @@ public class RequestInfoImpl implements IRequestInfo { } return authHeader; } else { - return "Bearer " + this.serviceAccountJwtClient.getIdToken(); + return "Bearer " + this.serviceAccountJwtClient.getIdToken(tenantInfo.getName()); } } } diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImpl.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..8e52738c46664d11cc87818bace77c5fa523e73a --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImpl.java @@ -0,0 +1,181 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.util; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.iam.v1.Iam; +import com.google.api.services.iam.v1.IamScopes; +import com.google.api.services.iam.v1.model.SignJwtRequest; +import com.google.api.services.iam.v1.model.SignJwtResponse; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.opendes.client.api.DpsHeaders; +import org.opendes.client.multitenancy.ITenantFactory; +import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.core.cache.JwtCache; +import org.opendes.core.logging.JaxRsDpsLog; +import org.opendes.core.model.IdToken; +import org.opendes.core.util.AppException; +import org.opendes.core.util.IServiceAccountJwtClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ServiceAccountJwtGcpClientImpl implements IServiceAccountJwtClient { + + private static final String JWT_AUDIENCE = "https://www.googleapis.com/oauth2/v4/token"; + private static final String SERVICE_ACCOUNT_NAME_FORMAT = "projects/%s/serviceAccounts/%s"; + + private final JsonFactory JSON_FACTORY = new JacksonFactory(); + + private Iam iam; + + @Autowired + private ITenantFactory tenantInfoServiceProvider; + @Autowired + private HeadersInfoGcpImpl headersInfoGcp; + @Autowired + private JwtCache cacheService; + @Autowired + private JaxRsDpsLog log; + + @Value("${GOOGLE_AUDIENCES}") + public String GOOGLE_AUDIENCES; + + @Value("${INDEXER_HOST}") + public String INDEXER_HOST; + + public String getIdToken(String tenantName) { + this.log.info("Tenant name received for auth token is: " + tenantName); + TenantInfo tenant = this.tenantInfoServiceProvider.getTenantInfo(tenantName); + if (tenant == null) { + this.log.error("Invalid tenant name receiving from pubsub"); + throw new AppException(HttpStatus.SC_BAD_REQUEST, "Invalid tenant Name", "Invalid tenant Name from pubsub"); + } + try { + + IdToken cachedToken = this.cacheService.get(tenant.getServiceAccount()); + this.headersInfoGcp.getHeaders().put(DpsHeaders.USER_EMAIL, tenant.getServiceAccount()); + + if (!IdToken.refreshToken(cachedToken)) { + return cachedToken.getTokenValue(); + } + + // Getting signed JWT + Map<String, Object> signJwtPayload = this.getJWTCreationPayload(tenant); + + SignJwtRequest signJwtRequest = new SignJwtRequest(); + signJwtRequest.setPayload(JSON_FACTORY.toString(signJwtPayload)); + + String serviceAccountName = String.format(SERVICE_ACCOUNT_NAME_FORMAT, tenant.getProjectId(), tenant.getServiceAccount()); + + Iam.Projects.ServiceAccounts.SignJwt signJwt = this.getIam().projects().serviceAccounts().signJwt(serviceAccountName, signJwtRequest); + SignJwtResponse signJwtResponse = signJwt.execute(); + String signedJwt = signJwtResponse.getSignedJwt(); + + // Getting id token + List<NameValuePair> postParameters = new ArrayList<>(); + postParameters.add(new BasicNameValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")); + postParameters.add(new BasicNameValuePair("assertion", signedJwt)); + + HttpPost post = new HttpPost(JWT_AUDIENCE); + post.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + post.setEntity(new UrlEncodedFormEntity(postParameters, "UTF-8")); + + try(CloseableHttpClient httpclient = HttpClients.createDefault(); + CloseableHttpResponse httpResponse = httpclient.execute(post)) { + JsonObject jsonContent = new JsonParser().parse(EntityUtils.toString(httpResponse.getEntity())).getAsJsonObject(); + + if (!jsonContent.has("id_token")) { + log.error(String.format("Google IAM response: %s", jsonContent.toString())); + throw new AppException(HttpStatus.SC_FORBIDDEN, "Access denied", "The user is not authorized to perform this action"); + } + + String token = jsonContent.get("id_token").getAsString(); + IdToken idToken = IdToken.builder().tokenValue(token).expirationTimeMillis(JWT.decode(token).getExpiresAt().getTime()).build(); + + this.cacheService.put(tenant.getServiceAccount(), idToken); + + return token; + } + } catch (JWTDecodeException e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Persistence error", "Invalid token, error decoding", e); + } catch (AppException e) { + throw e; + } catch (Exception e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Persistence error", "Error generating token", e); + } + } + + public Iam getIam() throws Exception { + + if (this.iam == null) { + HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + + // Authenticate using Google Application Default Credentials. + GoogleCredential credential = GoogleCredential.getApplicationDefault(); + if (credential.createScopedRequired()) { + List<String> scopes = new ArrayList<>(); + // Enable full Cloud Platform scope. + scopes.add(IamScopes.CLOUD_PLATFORM); + credential = credential.createScoped(scopes); + } + + // Create IAM API object associated with the authenticated transport. + this.iam = new Iam.Builder(httpTransport, JSON_FACTORY, credential) + .setApplicationName(INDEXER_HOST) + .build(); + } + + return this.iam; + } + + private Map<String, Object> getJWTCreationPayload(TenantInfo tenantInfo) { + + Map<String, Object> payload = new HashMap<>(); + String googleAudience = GOOGLE_AUDIENCES; + if (googleAudience.contains(",")) { + googleAudience = googleAudience.split(",")[0]; + } + payload.put("target_audience", googleAudience); + payload.put("exp", System.currentTimeMillis() / 1000 + 3600); + payload.put("iat", System.currentTimeMillis() / 1000); + payload.put("iss", tenantInfo.getServiceAccount()); + payload.put("aud", JWT_AUDIENCE); + + return payload; + } +} diff --git a/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/TraceIdExtractor.java b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/TraceIdExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..4d406e352bce61a242b823084e8b1ff3d79097bb --- /dev/null +++ b/indexer-service-gcp/src/main/java/org/opendes/indexer/gcp/util/TraceIdExtractor.java @@ -0,0 +1,63 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.util; + +import com.google.common.base.Strings; +import org.opendes.indexer.gcp.model.AppEngineHeaders; +import org.springframework.util.MultiValueMap; + +import java.util.Random; +import java.util.UUID; + +public class TraceIdExtractor { + + /* + * "X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE" e.g. 105445aa7843bc8bf206b120001000/0;o=1" + * https://cloud.google.com/trace/docs/support + * */ + public static String getTraceableCloudContext(MultiValueMap<String, String> requestHeaders) { + String traceContextHeader = requestHeaders.getFirst(AppEngineHeaders.CLOUD_TRACE_CONTEXT); + + // get new if not found + if (Strings.isNullOrEmpty(traceContextHeader)) return getNewTraceContext(); + // return as is + if (traceContextHeader.endsWith(";o=1")) return traceContextHeader; + + String[] traceParts = traceContextHeader.split("[/;]"); + // if there is only trace-id + if (traceParts.length == 1) return String.format("%s/%s;o=1", traceContextHeader, getNewSpanId()); + // if trace-id and span-id + if (traceParts.length == 2) return String.format("%s;o=1", traceContextHeader); + // trace flag is turned off + return String.format("%s/%s;o=1", traceParts[0], traceParts[1]); + } + + public static String getTraceId(String traceContextHeader) { + String[] traceParts = traceContextHeader.split("[/;]"); + return traceParts.length > 0 ? traceParts[0] : getNewTraceId(); + } + + private static String getNewTraceContext() { + return String.format("%s/%s;o=1", getNewTraceId(), getNewSpanId()); + } + + private static String getNewTraceId() { + return UUID.randomUUID().toString().replaceAll("-", ""); + } + + private static String getNewSpanId() { + return Integer.toUnsignedString(new Random().nextInt()); + } +} diff --git a/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImplTest.java b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0c2bd705dbadcef9ac69f1cc80d899523104d5bb --- /dev/null +++ b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/HeadersInfoGcpImplTest.java @@ -0,0 +1,198 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.util; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opendes.client.api.DpsHeaders; +import org.opendes.core.model.SlbHeaders; +import org.opendes.core.util.HeadersUtil; +import org.opendes.indexer.gcp.model.AppEngineHeaders; +import org.springframework.http.HttpHeaders; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +@RunWith(SpringRunner.class) +public class HeadersInfoGcpImplTest { + + @Mock + private HttpHeaders httpHeaders; + @InjectMocks + private HeadersInfoGcpImpl sut; + + @Test + public void should_convert_Cloud_Trace_when_header_contains_it() { + Map<String, String> requestHeaders = new HashMap<>(); + requestHeaders.put(DpsHeaders.AUTHORIZATION, "any token"); + requestHeaders.put(AppEngineHeaders.CLOUD_TRACE_CONTEXT, "any trace"); + + DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders); + + assertEquals("any token", map.getAuthorization()); + assertEquals("any trace", map.getHeaders().get(AppEngineHeaders.TRACE_ID)); + } + + @Test + public void should_return_header_logs_when_header_contains_email() { + MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.add(DpsHeaders.AUTHORIZATION, "any token"); + requestHeaders.add(DpsHeaders.ON_BEHALF_OF, "any onBehalf"); + requestHeaders.add(DpsHeaders.CORRELATION_ID, "any correlationId"); + requestHeaders.add(DpsHeaders.USER_EMAIL, "abc@xyz.com"); + requestHeaders.add(DpsHeaders.ACCOUNT_ID, "any account"); + + DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders.toSingleValueMap()); + assertEquals("any token", map.getAuthorization()); + DpsHeaders headers = DpsHeaders.createFromEntrySet(requestHeaders.entrySet()); + + assertEquals("account id: any account | on behalf: any onBehalf | user email: any onBehalf | correlation id: " + + "any correlationId", HeadersUtil.toLogMsg(headers, "any onBehalf")); + } + + @Test + public void should_return_header_logs_when_jwt_decode_fails() { + MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.add(DpsHeaders.AUTHORIZATION, "any token"); + requestHeaders.add(DpsHeaders.ON_BEHALF_OF, "any onBehalf"); + requestHeaders.add(DpsHeaders.CORRELATION_ID, "any correlationId"); + requestHeaders.add(DpsHeaders.ACCOUNT_ID, "any account"); + + DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders.toSingleValueMap()); + + assertEquals("any token", map.getAuthorization()); + + DpsHeaders headers = DpsHeaders.createFromEntrySet(requestHeaders.entrySet()); + assertEquals("account id: any account | on behalf: any onBehalf | correlation id: any correlationId", + HeadersUtil.toLogMsg(headers, null)); + } + + @Test + public void should_return_header_logs_when_header_doesNot_contain_email() { + Map<String, String> requestHeaders = new HashMap<>(); + requestHeaders.put(DpsHeaders.AUTHORIZATION, "any token"); + requestHeaders.put(DpsHeaders.ON_BEHALF_OF, "any onBehalf"); + requestHeaders.put(DpsHeaders.CORRELATION_ID, "any correlationId"); + requestHeaders.put(DpsHeaders.ACCOUNT_ID, "any account"); + + DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders); + assertEquals("any token", map.getHeaders().get(DpsHeaders.AUTHORIZATION)); + } + + @Test + public void check_correct_headers() { + Map<String, String> requestHeaders = new HashMap<>(); + requestHeaders.put(DpsHeaders.AUTHORIZATION, "any token"); + requestHeaders.put(DpsHeaders.CORRELATION_ID, "any correlationId"); + requestHeaders.put(DpsHeaders.ACCOUNT_ID, "any account"); + requestHeaders.put(DpsHeaders.ON_BEHALF_OF, "any onBehalf"); + requestHeaders.put(DpsHeaders.USER_EMAIL, "abc@xyz.com"); + requestHeaders.put(DpsHeaders.CONTENT_TYPE, "any contentType"); + requestHeaders.put(AppEngineHeaders.DATA_GROUPS, "any dataGrp"); + requestHeaders.put(AppEngineHeaders.CRON_SERVICE, "true"); + + DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders); + + assertEquals("any token", map.getAuthorization()); + assertEquals("any correlationId", map.getCorrelationId()); + assertEquals("abc@xyz.com", map.getUserEmail()); + assertEquals("any account", map.getPartitionIdWithFallbackToAccountId()); + assertEquals("any onBehalf", map.getOnBehalfOf()); + assertEquals("any contentType", map.getHeaders().get(DpsHeaders.CONTENT_TYPE)); + assertEquals("any dataGrp", map.getHeaders().get(AppEngineHeaders.DATA_GROUPS)); + assertEquals("true", map.getHeaders().get(AppEngineHeaders.CRON_SERVICE)); + } + + @Test + public void should_return_null_auth_header_when_invalid_header() { + Map<String, String> requestHeaders = new HashMap<>(); + requestHeaders.put(DpsHeaders.AUTHORIZATION, null); + + DpsHeaders map = this.sut.getCoreServiceHeaders(requestHeaders); + + assertNotNull(map); + assertNull(map.getAuthorization()); + } + + @Test + public void should_addCorrelationId_when_gettingHeaders() { + MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put(DpsHeaders.USER_EMAIL, singletonList("a@b.com")); + when(httpHeaders.toSingleValueMap()).thenReturn(requestHeaders.toSingleValueMap()); + + assertNotNull(sut.getHeaders().getCorrelationId()); + } + + @Test + public void should_returnUser_when_requested() { + MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put(DpsHeaders.USER_EMAIL, singletonList("a@b.com")); + when(httpHeaders.toSingleValueMap()).thenReturn(requestHeaders.toSingleValueMap()); + + assertEquals("a@b.com", sut.getUser()); + } + + @Test + public void should_returnPrimaryAccountId_when_requested() { + MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put(SlbHeaders.PRIMARY_PARTITION_ID, singletonList("apc")); + when(httpHeaders.toSingleValueMap()).thenReturn(requestHeaders.toSingleValueMap()); + + assertEquals("apc", sut.getPrimaryPartitionId()); + } + + @Test + public void should_convert_to_string_when_map_is_correct() { + MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.add("a", "a val"); + requestHeaders.add("b", "b val"); + requestHeaders.add(DpsHeaders.AUTHORIZATION, "blah"); + when(this.httpHeaders.toSingleValueMap()).thenReturn(requestHeaders.toSingleValueMap()); + + assertFalse(this.sut.toString().contains("a=a val")); + assertFalse(this.sut.toString().contains("b=b val")); + } + +// @Test +// public void should_convert_multivalued_map_to_hash_map_when_input_map_is_correct() { +// MultivaluedMap<String, String> requestHeaders = new MultivaluedMapImpl<String, String>(); +// requestHeaders.putSingle(DpsHeaders.AUTHORIZATION, "any token"); +// requestHeaders.putSingle(DpsHeaders.CORRELATION_ID, "any correlationId"); +// requestHeaders.putSingle(DpsHeaders.ACCOUNT_ID, "any account"); +// requestHeaders.putSingle(DpsHeaders.ON_BEHALF_OF, "any onBehalf"); +// requestHeaders.putSingle(DpsHeaders.USER_EMAIL, "abc@xyz.com"); +// requestHeaders.putSingle(DpsHeaders.CONTENT_TYPE, "any contentType"); +// requestHeaders.putSingle(AppEngineHeaders.DATA_GROUPS, "any dataGrp"); +// +// Map<String, String> map = this.sut.convertMultiToRegularMap(requestHeaders); +// +// assertEquals("any token", map.get(DpsHeaders.AUTHORIZATION)); +// assertEquals("any correlationId", map.get(DpsHeaders.CORRELATION_ID)); +// assertEquals("abc@xyz.com", map.get(DpsHeaders.USER_EMAIL)); +// assertEquals("any account", map.get(DpsHeaders.ACCOUNT_ID)); +// assertEquals("any onBehalf", map.get(DpsHeaders.ON_BEHALF_OF)); +// assertEquals("any contentType", map.get(DpsHeaders.CONTENT_TYPE)); +// assertEquals("any dataGrp", map.get(AppEngineHeaders.DATA_GROUPS)); +// } +} \ No newline at end of file diff --git a/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImplTest.java b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ec298fe9b6f1d0b88e5751fced1088e9dd4125ee --- /dev/null +++ b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/ServiceAccountJwtGcpClientImplTest.java @@ -0,0 +1,178 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.util; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.services.iam.v1.Iam; +import com.google.api.services.iam.v1.model.SignJwtResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.opendes.client.api.DpsHeaders; +import org.opendes.client.multitenancy.TenantInfo; +import org.opendes.core.cache.JwtCache; +import org.opendes.core.logging.JaxRsDpsLog; +import org.opendes.core.model.DeploymentEnvironment; +import org.opendes.core.model.IdToken; +import org.opendes.core.service.TenantInfoServiceImpl; +import org.opendes.core.util.AppException; +import org.opendes.core.util.Config; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.MockitoAnnotations.initMocks; +import static org.powermock.api.mockito.PowerMockito.when; + +@Ignore +@RunWith(SpringRunner.class) +@PrepareForTest({GoogleNetHttpTransport.class, GoogleCredential.class, NetHttpTransport.class, SignJwtResponse.class, Iam.Builder.class, HttpClients.class, EntityUtils.class, Config.class}) +public class ServiceAccountJwtGcpClientImplTest { + + private static final String JWT_TOKEN = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik1UVXlPREE0TXpFd09BPT0ifQ.eyJzdWIiOiJtemh1OUBzbGIuY29tIiwiaXNzIjoic2F1dGgtcHJldmlldy5zbGIuY29tIiwiYXVkIjoidGVzdC1zbGJkZXYtZGV2cG9ydGFsLnNsYmFwcC5jb20iLCJpYXQiOjE1MjgxNDg5MTUsImV4cCI6MTUyODIzNTMxNSwicHJvdmlkZXIiOiJzbGIuY29tIiwiY2xpZW50IjoidGVzdC1zbGJkZXYtZGV2cG9ydGFsLnNsYmFwcC5jb20iLCJ1c2VyaWQiOiJtemh1OUBzbGIuY29tIiwiZW1haWwiOiJtemh1OUBzbGIuY29tIiwiYXV0aHoiOiJ7XCJhY2NvdW50Q291bnRyeVwiOntcImNvZGVcIjpcInVzXCIsXCJpZFwiOjU3MTU5OTkxMDE4MTI3MzYsXCJuYW1lXCI6XCJVbml0ZWQgU3RhdGVzIG9mIEFtZXJpY2FcIn0sXCJhY2NvdW50SWRcIjo1NjkxODc4ODMzOTEzODU2LFwiYWNjb3VudE5hbWVcIjpcIlNJUyBJbnRlcm5hbCBIUVwiLFwiY3JlYXRlZFwiOlwiMjAxOC0wNS0wM1QxNzoyNTo1NS40NDNaXCIsXCJkZXBhcnRtZW50TWFuYWdlclwiOm51bGwsXCJzdWJzY3JpcHRpb25zXCI6W3tcImFjY291bnRJZFwiOjU2OTE4Nzg4MzM5MTM4NTYsXCJjb250cmFjdElkXCI6NTc1MTcwMDIxMjE1NDM2OCxcImNyZWF0ZWRcIjpcIjIwMTgtMDUtMDNUMTc6MzM6MDkuNTczWlwiLFwiY3JtQ29udHJhY3RJZFwiOlwiU0lTLUlOVEVSTkFMLUhRLVFBXCIsXCJjcm1Db250cmFjdEl0ZW1JZFwiOlwiZGV2bGlcIixcImV4cGlyYXRpb25cIjpcIjE5NzAtMDEtMDFUMDA6MDA6MDAuMDAwWlwiLFwiaWRcIjo1MDc5Mjg4NTA0MTIzMzkyLFwicHJvZHVjdFwiOntcImNvZGVcIjpcImRldmVsb3Blci1saWdodFwiLFwiY29tY2F0TmFtZVwiOlwiTm90IGluIENvbUNhdFwiLFwiZmVhdHVyZVNldHNcIjpbe1wiYXBwbGljYXRpb25cIjp7XCJjb2RlXCI6XCJhcGlkZXZlbG9wZXJwb3J0YWxcIixcImlkXCI6NTE2ODkzMDY5NTkzODA0OCxcIm5hbWVcIjpcIkFQSSBEZXZlbG9wZXIgUG9ydGFsXCIsXCJ0eXBlXCI6XCJXZWJBcHBcIn0sXCJjbGFpbXNcIjpudWxsLFwiaWRcIjo1MTkxNTcyMjg3MTI3NTUyLFwibmFtZVwiOlwiRGV2ZWxvcGVyXCIsXCJ0eXBlXCI6XCJCQVNFXCJ9XSxcImlkXCI6NTE1MDczMDE1MTI2NDI1NixcIm5hbWVcIjpcIkRldmVsb3BlciBQb3J0YWxcIixcInBhcnROdW1iZXJcIjpcIlNERUwtUEItU1VCVVwifX1dLFwidXNlckVtYWlsXCI6XCJtemh1OUBzbGIuY29tXCIsXCJ1c2VyTmFtZVwiOlwiTWluZ3lhbmcgWmh1XCJ9XG4iLCJsYXN0bmFtZSI6IlpodSIsImZpcnN0bmFtZSI6Ik1pbmd5YW5nIiwiY291bnRyeSI6IiIsImNvbXBhbnkiOiIiLCJqb2J0aXRsZSI6IiIsInN1YmlkIjoiNDE3YjczMjktYmMwNy00OTFmLWJiYzQtZTQ1YjRhMWFiYjVjLVd3U0c0dyIsImlkcCI6ImNvcnAyIiwiaGQiOiJzbGIuY29tIn0.WQfGr1Xu-6IdaXdoJ9Fwzx8O2el1UkFPWo1vk_ujiAfdOjAR46UG5SrBC7mzC7gYRyK3a4fimBmbv3uRVJjTNXdxXRLZDw0SvXUMIOqjUGLom491ESbrtka_Xz7vGO-tWyDcEQDTfFzQ91LaVN7XdzL18_EDTXZoPhKb-zquyk9WLQxP9Mw-3Yh-UrbvC9nl1-GRn1IVbzp568kqkpOVUFM9alYSGw-oMGDZNt1DIYOJnpGaw2RB5B3AKvNivZH_Xdac7ZTzQbsDOt8B8DL2BphuxcJ9jshCJkM2SHQ15uErv8sfnzMwdF08e_0QcC_30I8eX9l8yOu6TnwwqlXunw"; + + @Mock + private JaxRsDpsLog log; + @Mock + private GoogleCredential credential; + @Mock + private NetHttpTransport httpTransport; + @Mock + private SignJwtResponse signJwtResponse; + @Mock + private Iam iam; + @Mock + private Iam.Projects iamProject; + @Mock + private Iam.Projects.ServiceAccounts iamProjectServiceAccounts; + @Mock + private Iam.Projects.ServiceAccounts.SignJwt signJwt; + @Mock + private CloseableHttpClient httpClient; + @Mock + private CloseableHttpResponse httpResponse; + @InjectMocks + private TenantInfoServiceImpl tenantInfoServiceProvider; + @Mock + private TenantInfoServiceImpl tenantInfoService; + @Mock + private JwtCache cacheService; + @Mock + private HeadersInfoGcpImpl headersInfoGcp; + @InjectMocks @Spy + private ServiceAccountJwtGcpClientImpl sut; + @Before + public void setup() throws Exception { + initMocks(this); + +// mockStatic(GoogleNetHttpTransport.class); +// mockStatic(GoogleCredential.class); +// mockStatic(HttpClients.class); +// mockStatic(EntityUtils.class); +// mockStatic(Config.class); + + when(GoogleNetHttpTransport.newTrustedTransport()).thenReturn(httpTransport); + when(GoogleCredential.getApplicationDefault()).thenReturn(credential); + when(credential.createScopedRequired()).thenReturn(true); + when(credential.createScoped(any())).thenReturn(credential); + when(HttpClients.createDefault()).thenReturn(httpClient); + when(httpClient.execute(any())).thenReturn(httpResponse); + when(Config.getDeploymentEnvironment()).thenReturn(DeploymentEnvironment.LOCAL); + when(Config.getGoogleAudiences()).thenReturn("aud"); + + when(this.tenantInfoServiceProvider).thenReturn(this.tenantInfoService); + + TenantInfo tenantInfo = new TenantInfo(); + tenantInfo.setServiceAccount("tenant"); + when(this.tenantInfoService.getTenantInfo()).thenReturn(tenantInfo); + + when(this.sut.getIam()).thenReturn(iam); + when(this.iam.projects()).thenReturn(iamProject); + when(this.iamProject.serviceAccounts()).thenReturn(iamProjectServiceAccounts); + when(this.iamProjectServiceAccounts.signJwt(any(), any())).thenReturn(signJwt); + when(this.signJwt.execute()).thenReturn(signJwtResponse); + when(this.signJwtResponse.getSignedJwt()).thenReturn("testJwt"); + + Map<String, String> headers = new HashMap<>(); + DpsHeaders dpsHeaders = DpsHeaders.createFromMap(headers); + when(this.headersInfoGcp.getHeaders()).thenReturn(dpsHeaders); + } + + @Test + public void should_returnCachedToken_givenCachedToken_getIdTokenTest() { + String tokenValue = "tokenValue"; + IdToken idToken = IdToken.builder().tokenValue(tokenValue).expirationTimeMillis(System.currentTimeMillis() + 10000000L).build(); + when(this.cacheService.get(any())).thenReturn(idToken); + + String returnedIdToken = this.sut.getIdToken(tokenValue); + + Assert.assertEquals(tokenValue, returnedIdToken); + } + + @Test + public void should_returnValidToken_getIdTokenTest() throws Exception { + when(EntityUtils.toString(any())).thenReturn(String.format("{\"id_token\":\"%s\"}", JWT_TOKEN)); + + String returnedToken = this.sut.getIdToken("tenant"); + + Assert.assertEquals(JWT_TOKEN, returnedToken); + } + + @Test + public void should_return500_given_invalidJWTResponse_getIdTokenException() { + try { + when(EntityUtils.toString(any())).thenReturn(String.format("{\"id_token\":\"%s\"}", "invalid jwt")); + + this.sut.getIdToken("tenant"); + fail("Should throw exception"); + } catch (AppException e) { + Assert.assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getError().getCode()); + Assert.assertEquals("Invalid token, error decoding", e.getError().getMessage()); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } + + @Test + public void should_return403_given_missingIdTokenResponse_getIdTokenException() { + try { + when(EntityUtils.toString(any())).thenReturn("{}"); + + this.sut.getIdToken("tenant"); + fail("Should throw exception"); + } catch (AppException e) { + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, e.getError().getCode()); + Assert.assertEquals("The user is not authorized to perform this action", e.getError().getMessage()); + } catch (Exception e) { + fail("Should not throw this exception" + e.getMessage()); + } + } +} diff --git a/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/TraceIdExtractorTest.java b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/TraceIdExtractorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..09cf784b63ea2a5aea559d7de72081f75547ea87 --- /dev/null +++ b/indexer-service-gcp/src/test/java/org/opendes/indexer/gcp/util/TraceIdExtractorTest.java @@ -0,0 +1,71 @@ +// Copyright 2017-2019, Schlumberger +// +// 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.opendes.indexer.gcp.util; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.opendes.client.api.DpsHeaders; +import org.opendes.indexer.gcp.model.AppEngineHeaders; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +import java.util.ArrayList; +import java.util.List; + +import static junit.framework.TestCase.assertTrue; + +@RunWith(SpringRunner.class) +public class TraceIdExtractorTest { + + @Test + public void should_getTraceableCloudContext_when_header_doesNot_contains_it() { + List<String> token = new ArrayList<>(); + token.add("any token"); + MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put(DpsHeaders.AUTHORIZATION, token); + + String actual = TraceIdExtractor.getTraceableCloudContext(requestHeaders); + + assertTrue(actual.matches(".*o=1")); + } + + @Test + public void should_getTraceableCloudContext_when_header_contains_it() { + List<String> token = new ArrayList<>(); + token.add("any token"); + List<String> trace = new ArrayList<>(); + trace.add("any trace"); + MultiValueMap<String, String> requestHeaders = new LinkedMultiValueMap<>(); + requestHeaders.put(DpsHeaders.AUTHORIZATION, token); + requestHeaders.put(AppEngineHeaders.CLOUD_TRACE_CONTEXT, trace); + + String actual = TraceIdExtractor.getTraceableCloudContext(requestHeaders); + assertTrue(actual.matches("any trace/.*o=1")); + + trace = new ArrayList<>(); + trace.add("any/trace"); + requestHeaders.put(AppEngineHeaders.CLOUD_TRACE_CONTEXT, trace); + actual = TraceIdExtractor.getTraceableCloudContext(requestHeaders); + assertTrue(actual.matches("any/trace;o=1")); + + trace = new ArrayList<>(); + trace.add("any/trace/test"); + requestHeaders.put(AppEngineHeaders.CLOUD_TRACE_CONTEXT, trace); + actual = TraceIdExtractor.getTraceableCloudContext(requestHeaders); + assertTrue(actual.matches("any/trace;o=1")); + } + +} diff --git a/indexer-service-gcp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/indexer-service-gcp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000000000000000000000000000000000..ca6ee9cea8ec189a088d50559325d4e84ff8ad09 --- /dev/null +++ b/indexer-service-gcp/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/indexer-service-root/pom.xml b/indexer-service-root/pom.xml index 8736a330674b9d55b99fae0bda4aed78db43da2e..6b0143bdbb4adb8175aa54ca15e54ed2b36fc0af 100644 --- a/indexer-service-root/pom.xml +++ b/indexer-service-root/pom.xml @@ -33,14 +33,23 @@ <artifactId>indexer-search-core-root</artifactId> <version>1.0.0</version> </dependency> + <!-- spring boot dependencies --> <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-actuator</artifactId> + <groupId>org.apache.tomcat.embed</groupId> + <artifactId>tomcat-embed-core</artifactId> + <version>9.0.21</version> </dependency> + <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-data-redis</artifactId> + <artifactId>spring-boot-starter-web</artifactId> + <exclusions> + <exclusion> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-tomcat</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> @@ -48,7 +57,7 @@ </dependency> <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-oauth2-client</artifactId> + <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> @@ -56,15 +65,7 @@ </dependency> <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-web</artifactId> - </dependency> - <dependency> - <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-starter-oauth2</artifactId> - </dependency> - <dependency> - <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-starter-security</artifactId> + <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> @@ -83,12 +84,6 @@ <version>6.6.2</version> </dependency> - - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-tomcat</artifactId> - <scope>provided</scope> - </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> @@ -126,18 +121,6 @@ </exclusions> </dependency> - <!-- https://mvnrepository.com/artifact/io.github.resilience4j/resilience4j-circuitbreaker --> - <dependency> - <groupId>io.github.resilience4j</groupId> - <artifactId>resilience4j-circuitbreaker</artifactId> - <version>0.17.0</version> - </dependency> - <!-- https://mvnrepository.com/artifact/io.github.resilience4j/resilience4j-retry --> - <dependency> - <groupId>io.github.resilience4j</groupId> - <artifactId>resilience4j-retry</artifactId> - <version>0.17.0</version> - </dependency> <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang --> <dependency> <groupId>commons-lang</groupId> @@ -157,133 +140,6 @@ <version>${springfox-version}</version> </dependency> - <!-- Test Dependencies --> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-test</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.springframework.security</groupId> - <artifactId>spring-security-test</artifactId> - <scope>test</scope> - </dependency> - - - <!-- Test Dependencies --> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.12</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-junit</artifactId> - <version>2.0.0.0</version> - <scope>test</scope> - </dependency> - <!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito2 --> - <dependency> - <groupId>org.powermock</groupId> - <artifactId>powermock-api-mockito2</artifactId> - <version>2.0.2</version> - <scope>test</scope> - </dependency> - <!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 --> - <dependency> - <groupId>org.powermock</groupId> - <artifactId>powermock-module-junit4</artifactId> - <version>2.0.2</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-core</artifactId> - <version>3.0.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.codehaus.mojo</groupId> - <artifactId>cobertura-maven-plugin</artifactId> - <version>2.7</version> - <scope>test</scope> - </dependency> - - <!-- https://mvnrepository.com/artifact/com.github.stefanbirkner/system-rules --> - <dependency> - <groupId>com.github.stefanbirkner</groupId> - <artifactId>system-rules</artifactId> - <version>1.2.0</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>com.google.api.grpc</groupId> - <artifactId>proto-google-cloud-pubsub-v1</artifactId> - <version>1.60.0</version> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>com.google.api.grpc</groupId> - <artifactId>proto-google-cloud-pubsub-v1</artifactId> - <version>1.60.0</version> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>com.google.api.grpc</groupId> - <artifactId>proto-google-cloud-pubsub-v1</artifactId> - <version>1.60.0</version> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>org.opendes.core</groupId> - <artifactId>indexer-search-core-root</artifactId> - <version>1.0.0</version> - </dependency> - <dependency> - <groupId>org.opendes.core</groupId> - <artifactId>indexer-search-core-root</artifactId> - <version>1.0.0</version> - </dependency> - <dependency> - <groupId>org.opendes.core</groupId> - <artifactId>indexer-search-core-root</artifactId> - <version>1.0.0</version> - </dependency> </dependencies> - <dependencyManagement> - <dependencies> - <dependency> - <groupId>org.springframework.cloud</groupId> - <artifactId>spring-cloud-dependencies</artifactId> - <version>${spring-cloud.version}</version> - <type>pom</type> - <scope>import</scope> - </dependency> - </dependencies> - </dependencyManagement> - -<!-- <build>--> -<!-- <plugins>--> -<!-- <plugin>--> -<!-- <groupId>org.springframework.boot</groupId>--> -<!-- <artifactId>spring-boot-maven-plugin</artifactId>--> -<!-- <executions>--> -<!-- <execution>--> -<!-- <goals>--> -<!-- <goal>repackage</goal>--> -<!-- </goals>--> -<!-- <configuration>--> -<!-- <classifier>spring-boot</classifier>--> -<!-- <mainClass>--> -<!-- org.opendes.indexer.IndexerApplication--> -<!-- </mainClass>--> -<!-- </configuration>--> -<!-- </execution>--> -<!-- </executions>--> -<!-- </plugin>--> -<!-- </plugins>--> -<!-- </build>--> - </project> diff --git a/indexer-service-root/src/main/java/org/opendes/indexer/SwaggerDoc.java b/indexer-service-root/src/main/java/org/opendes/indexer/SwaggerDoc.java index d0c9a387a220dd8f406253a2650f3a61a00251b0..4894e1df574b927c0402aa2c10786786528765ac 100644 --- a/indexer-service-root/src/main/java/org/opendes/indexer/SwaggerDoc.java +++ b/indexer-service-root/src/main/java/org/opendes/indexer/SwaggerDoc.java @@ -145,4 +145,8 @@ public final class SwaggerDoc { // REQUEST VALIDATION public static final String REQUEST_VALIDATION_NOT_NULL_BODY = "Request body can not be null"; + + // Azure Schema Request + public static final String SCHEMA_REQUEST_KIND = "Record kind for which the schema information is applied to."; + public static final String RECORD_KIND_EXAMPLE = "common:welldb:wellbore:1.0.0"; } diff --git a/indexer-service-root/src/main/java/org/opendes/indexer/model/PublishMessage.java b/indexer-service-root/src/main/java/org/opendes/indexer/model/PublishMessage.java new file mode 100644 index 0000000000000000000000000000000000000000..280958b2efe9bd4af043f2722e4e5014c3b44e77 --- /dev/null +++ b/indexer-service-root/src/main/java/org/opendes/indexer/model/PublishMessage.java @@ -0,0 +1,5 @@ +package org.opendes.indexer.model; + +public class PublishMessage { + +} diff --git a/indexer-service-root/src/main/java/org/opendes/indexer/publish/IPublisher.java b/indexer-service-root/src/main/java/org/opendes/indexer/publish/IPublisher.java index 6c592cfba6fd838e7d262ffc6e9790bcf9508edb..ef8709d9f6f65e0931c9f8d3ea890da8c1644fa4 100644 --- a/indexer-service-root/src/main/java/org/opendes/indexer/publish/IPublisher.java +++ b/indexer-service-root/src/main/java/org/opendes/indexer/publish/IPublisher.java @@ -1,10 +1,9 @@ package org.opendes.indexer.publish; -import com.google.pubsub.v1.PubsubMessage; import org.opendes.client.api.DpsHeaders; import org.opendes.indexer.util.JobStatus; public interface IPublisher { - public PubsubMessage publishStatusChangedTagsToTopic(DpsHeaders headers, JobStatus indexerBatchStatus) throws Exception; + public void publishStatusChangedTagsToTopic(DpsHeaders headers, JobStatus indexerBatchStatus) throws Exception; } diff --git a/indexer-service-root/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/indexer-service-root/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000000000000000000000000000000000..ca6ee9cea8ec189a088d50559325d4e84ff8ad09 --- /dev/null +++ b/indexer-service-root/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2cf8e36ae6c3e867b89eee55dd635a605ebb5f2f..151d85522e1fd6c95d270a9dd25817f5392a13bb 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,66 @@ <!-- <springfox-version>2.7.0</springfox-version>--> </properties> + <repositories> + <repository> + <id>dev-azure-com-slb-des-ext-collaboration-os-core</id> + <url>https://pkgs.dev.azure.com/slb-des-ext-collaboration/_packaging/os-core/maven/v1</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + </repositories> + <!-- Test Dependencies --> + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.springframework.security</groupId> + <artifactId>spring-security-test</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + <!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito2 --> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito2</artifactId> + <version>2.0.2</version> + <scope>test</scope> + </dependency> + + <!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 --> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4</artifactId> + <version>2.0.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>3.0.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.codehaus.mojo</groupId> + <artifactId>cobertura-maven-plugin</artifactId> + <version>2.7</version> + <scope>test</scope> + </dependency> + </dependencies> <profiles> <profile>