diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4d78da942d090b651569fefaa892967de2f887a0..2fc1917708e58e1c62166e76c2f77bd8cde0f15a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,14 +4,17 @@ variables: OSDU_GCP_VENDOR: gcp OSDU_GCP_SERVICE: notification OSDU_GCP_REGISTER_BASE_URL: https://os-register-attcrcktoa-uc.a.run.app/api/register/v1 - OSDU_GCP_ENV_VARS: APP_PROJECT=$OSDU_GCP_PROJECT,APP_ENTITLEMENTS=$OSDU_GCP_ENTITLEMENTS_URL,APP_REGISTER=$OSDU_GCP_REGISTER_BASE_URL,APP_GOOGLEAUDIENCE=$GOOGLE_AUDIENCE + OSDU_GCP_ENV_VARS: APP_PROJECT=$OSDU_GCP_PROJECT,APP_ENTITLEMENTS=$OSDU_GCP_ENTITLEMENTS_V2_URL,APP_REGISTER=$OSDU_GCP_REGISTER_BASE_URL,APP_GOOGLEAUDIENCE=$GOOGLE_AUDIENCE,PARTITION_API=$OSDU_GCP_PARTITION_API,GOOGLE_AUDIENCES=$GOOGLE_AUDIENCE OSDU_GCP_ENVIRONMENT: dev_gke OSDU_GCP_LOG_LEVEL: INFO + OSDU_GCP_HELM_PACKAGE_CHARTS: "devops/gcp/deploy devops/gcp/configmap" AWS_BUILD_SUBDIR: provider/notification-aws/build-aws AWS_TEST_SUBDIR: testing/notification-test-aws AWS_SERVICE: notification AWS_ENVIRONMENT: dev + AWS_DEPLOY_TARGET: EKS + AWS_EKS_DEPLOYMENT_NAME: os-notification AZURE_SERVICE: notification AZURE_BUILD_SUBDIR: provider/notification-azure diff --git a/NOTICE b/NOTICE index 9bcef882e976b6025479d4d9691d0a847a57e4ad..31690502fe7bc60b37552c42ae2d6705b1460c08 100644 --- a/NOTICE +++ b/NOTICE @@ -9,13 +9,6 @@ The following software have components provided under the terms of this license: - Android SDK (from https://www.android.com/) -======================================================================== -Apache-1.1 -======================================================================== -The following software have components provided under the terms of this license: - -- StAX (from http://stax.codehaus.org/) - ======================================================================== Apache-2.0 ======================================================================== @@ -23,22 +16,7 @@ The following software have components provided under the terms of this license: - AMQP 1.0 JMS Spring Boot AutoConfiguration (from https://repo1.maven.org/maven2/org/amqphub/spring/amqp-10-jms-spring-boot-autoconfigure) - AMQP 1.0 JMS Spring Boot Starter (from https://repo1.maven.org/maven2/org/amqphub/spring/amqp-10-jms-spring-boot-starter) -- ASM Core (from ) - ASM based accessors helper used by json-smart (from ) -- AWS Event Stream (from https://github.com/awslabs/aws-eventstream-java) -- AWS Java SDK :: AWS Core (from https://aws.amazon.com/sdkforjava) -- AWS Java SDK :: Annotations (from ) -- AWS Java SDK :: Auth (from https://aws.amazon.com/sdkforjava) -- AWS Java SDK :: Core :: Protocols :: AWS Json Protocol (from https://aws.amazon.com/sdkforjava) -- AWS Java SDK :: Core :: Protocols :: Protocol Core (from https://aws.amazon.com/sdkforjava) -- AWS Java SDK :: HTTP Client Interface (from ) -- AWS Java SDK :: HTTP Clients :: Apache (from ) -- AWS Java SDK :: HTTP Clients :: Netty Non-Blocking I/O (from ) -- AWS Java SDK :: Profiles (from https://aws.amazon.com/sdkforjava) -- AWS Java SDK :: Regions (from ) -- AWS Java SDK :: SDK Core (from https://aws.amazon.com/sdkforjava) -- AWS Java SDK :: Services :: AWS Simple Systems Management (SSM) (from https://aws.amazon.com/sdkforjava) -- AWS Java SDK :: Utilities (from ) - AWS Java SDK for AWS Elemental MediaLive (from https://aws.amazon.com/sdkforjava) - AWS Java SDK for AWS KMS (from https://aws.amazon.com/sdkforjava) - AWS Java SDK for AWS Lambda (from https://aws.amazon.com/sdkforjava) @@ -51,266 +29,280 @@ The following software have components provided under the terms of this license: - AWS Java SDK for Amazon SNS (from https://aws.amazon.com/sdkforjava) - AWS Java SDK for Amazon SQS (from https://aws.amazon.com/sdkforjava) - AWS Java SDK for the AWS Simple Systems Management (SSM) Service (from https://aws.amazon.com/sdkforjava) -- AWS SDK for Java - BOM (from https://aws.amazon.com/sdkforjava) - AWS SDK for Java - Core (from https://aws.amazon.com/sdkforjava) -- Adapter: RxJava (from ) -- Apache Commons Codec (from http://commons.apache.org/proper/commons-codec/) +- Adapter: RxJava (from https://repo1.maven.org/maven2/com/squareup/retrofit2/adapter-rxjava) +- Apache Commons Codec (from https://commons.apache.org/proper/commons-codec/) - Apache Commons Collections (from http://commons.apache.org/proper/commons-collections/) +- Apache Commons IO (from https://commons.apache.org/proper/commons-io/) - Apache Commons Lang (from http://commons.apache.org/proper/commons-lang/) - Apache Commons Logging (from http://commons.apache.org/proper/commons-logging/) - Apache Commons Logging (from http://commons.apache.org/proper/commons-logging/) - Apache Geronimo JMS Spec 2.0 (from http://geronimo.apache.org/maven/${siteId}/${version}) - Apache HttpAsyncClient (from http://hc.apache.org/httpcomponents-asyncclient) -- Apache HttpClient (from http://hc.apache.org/httpcomponents-client) -- Apache HttpClient Cache (from http://hc.apache.org/httpcomponents-client) -- Apache HttpCore (from http://hc.apache.org/httpcomponents-core-ga) - Apache HttpCore NIO (from http://hc.apache.org/httpcomponents-core-ga) -- Apache Log4j API (from ) -- Apache Log4j Core (from ) -- Apache Log4j JUL Adapter (from ) -- Apache Log4j SLF4J Binding (from ) -- Apache Log4j to SLF4J Adapter (from ) -- AssertJ fluent assertions (from ) -- Asynchronous Http Client (from ) -- Asynchronous Http Client Netty Utils (from ) -- Azure AD Spring Security Integration Spring Boot Starter (from https://github.com/Microsoft/azure-spring-boot) -- Azure Metrics Spring Boot Starter (from https://github.com/Microsoft/azure-spring-boot) +- Apache Log4j API (from https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api) +- Apache Log4j Core (from https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core) +- Apache Log4j JUL Adapter (from https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-jul) +- Apache Log4j SLF4J Binding (from https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-slf4j-impl) +- Apache Log4j to SLF4J Adapter (from https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-to-slf4j) +- AssertJ fluent assertions (from https://repo1.maven.org/maven2/org/assertj/assertj-core) +- Asynchronous Http Client (from https://repo1.maven.org/maven2/org/asynchttpclient/async-http-client) +- Asynchronous Http Client Netty Utils (from https://repo1.maven.org/maven2/org/asynchttpclient/async-http-client-netty-utils) +- AutoValue Annotations (from https://github.com/google/auto/tree/master/value) +- Azure Spring Boot Starter for Azure AD Spring Security Integration (from https://github.com/Azure/azure-sdk-for-java) - Bean Validation API (from http://beanvalidation.org) -- Byte Buddy (without dependencies) (from ) -- Byte Buddy Java agent (from ) +- Brave (from https://repo1.maven.org/maven2/io/zipkin/brave/brave) +- Brave Instrumentation: Http Adapters (from https://repo1.maven.org/maven2/io/zipkin/brave/brave-instrumentation-http) +- Brave instrumentation for Reactor Netty HTTP (from https://github.com/reactor/reactor-netty) +- Byte Buddy (without dependencies) (from https://repo1.maven.org/maven2/net/bytebuddy/byte-buddy) +- Byte Buddy agent (from https://repo1.maven.org/maven2/net/bytebuddy/byte-buddy-agent) - ClassMate (from http://github.com/cowtowncoder/java-classmate) -- Cloud Storage JSON API v1-rev58-1.21.0 (from ) -- Commons IO (from http://commons.apache.org/io/) -- Converter: Jackson (from ) -- Elastic JNA Distribution (from https://github.com/java-native-access/jna) -- Elasticsearch: 5.0.0-alpha5 (from https://github.com/elastic/elasticsearch) +- Cloud Storage JSON API v1-rev20210914-1.32.1 (from https://repo1.maven.org/maven2/com/google/apis/google-api-services-storage) +- Converter: Jackson (from https://repo1.maven.org/maven2/com/squareup/retrofit2/converter-jackson) +- Core functionality for the Reactor Netty library (from https://github.com/reactor/reactor-netty) +- Elastic JNA Distribution (from https://github.com/elastic/jna-build) - Expression Language 3.0 (from http://uel.java.net) -- Expression Language 3.0 (from https://projects.eclipse.org/projects/ee4j.el) - FindBugs-jsr305 (from http://findbugs.sourceforge.net/) -- Google APIs Client Library for Java (from ) -- Google App Engine extensions to the Google HTTP Client Library for Java. (from ) -- Google Cloud Core (from https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-core) -- Google Cloud Core HTTP (from https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-core-http) -- Google Cloud Core gRPC (from https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-core-grpc) -- Google Cloud Datastore (from https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-datastore) -- Google Cloud Key Management Service (KMS) API v1-rev22-1.23.0 (from ) -- Google Cloud Logging (from https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-logging) -- Google Cloud Pub/Sub (from https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-pubsub) -- Google Cloud Storage (from https://github.com/GoogleCloudPlatform/google-cloud-java/tree/master/google-cloud-storage) -- Google HTTP Client Library for Java (from https://github.com/google/google-http-java-client.git) -- Google HTTP Client Library for Java (from https://github.com/google/google-http-java-client.git) -- Google OAuth Client Library for Java (from ) -- Gson (from https://github.com/google/gson) -- Guava InternalFutureFailureAccess and InternalFutures (from ) -- Guava InternalFutureFailureAccess and InternalFutures (from ) -- Guava ListenableFuture only (from ) -- Guava: Google Core Libraries for Java (from https://github.com/google/guava.git) -- Guava: Google Core Libraries for Java (from https://github.com/google/guava.git) -- HPPC Collections (from http://labs.carrotsearch.com) -- Hibernate Validator Engine (from ) -- Hibernate Validator Engine (from ) +- GSON extensions to the Google HTTP Client Library for Java. (from https://repo1.maven.org/maven2/com/google/http-client/google-http-client-gson) +- Google APIs Client Library for Java (from https://repo1.maven.org/maven2/com/google/api-client/google-api-client) +- Google App Engine extensions to the Google HTTP Client Library for Java. (from https://repo1.maven.org/maven2/com/google/http-client/google-http-client-appengine) +- Google Cloud Core (from https://github.com/googleapis/java-core) +- Google Cloud Core HTTP (from https://github.com/googleapis/java-core) +- Google Cloud Core gRPC (from https://github.com/googleapis/google-cloud-java/tree/master/google-cloud-clients/google-cloud-core-grpc) +- Google Cloud Datastore (from https://github.com/googleapis/java-datastore) +- Google Cloud IAM Service Account Credentials (from https://github.com/googleapis/java-iamcredentials) +- Google Cloud Key Management Service (KMS) API v1-rev9-1.22.0 (from https://repo1.maven.org/maven2/com/google/apis/google-api-services-cloudkms) +- Google Cloud Logging (from https://github.com/googleapis/google-cloud-java/tree/master/google-cloud-clients/google-cloud-logging) +- Google Cloud Pub/Sub (from https://github.com/googleapis/java-pubsub) +- Google Cloud Storage (from https://github.com/googleapis/java-storage) +- Google HTTP Client Library for Java (from https://repo1.maven.org/maven2/com/google/http-client/google-http-client) +- Google HTTP Client Library for Java (from https://repo1.maven.org/maven2/com/google/http-client/google-http-client) +- Google OAuth Client Library for Java (from https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client) +- Gson (from https://repo1.maven.org/maven2/com/google/code/gson/gson) +- Gson (from https://repo1.maven.org/maven2/com/google/code/gson/gson) +- Guava InternalFutureFailureAccess and InternalFutures (from https://repo1.maven.org/maven2/com/google/guava/failureaccess) +- Guava InternalFutureFailureAccess and InternalFutures (from https://repo1.maven.org/maven2/com/google/guava/failureaccess) +- Guava: Google Core Libraries for Java (from https://github.com/google/guava) +- Guava: Google Core Libraries for Java (from https://github.com/google/guava) +- HPPC Collections (from https://repo1.maven.org/maven2/com/carrotsearch/hppc) +- HTTP functionality for the Reactor Netty library (from https://github.com/reactor/reactor-netty) +- Hibernate Validator Engine (from https://repo1.maven.org/maven2/org/hibernate/validator/hibernate-validator) +- Hibernate Validator Engine (from https://repo1.maven.org/maven2/org/hibernate/validator/hibernate-validator) +- HttpClient (from http://hc.apache.org/httpcomponents-client) +- HttpClient Cache (from http://hc.apache.org/httpcomponents-client) +- HttpCore (from http://hc.apache.org/httpcomponents-core-ga) - IBM COS Java SDK for Amazon S3 (from https://github.com/ibm/ibm-cos-sdk-java) - IBM COS Java SDK for COS KMS (from https://github.com/ibm/ibm-cos-sdk-java) - IBM COS SDK For Java (from https://github.com/ibm/ibm-cos-sdk-java) - IBM COS SDK for Java - Core (from https://github.com/ibm/ibm-cos-sdk-java) -- Identity and Access Management (IAM) API v1-rev247-1.23.0 (from ) -- Identity and Access Management (IAM) API v1-rev247-1.23.0 (from ) -- IntelliJ IDEA Annotations (from http://www.jetbrains.org) +- Identity and Access Management (IAM) API v1-rev284-1.25.0 (from https://repo1.maven.org/maven2/com/google/apis/google-api-services-iam) +- Identity and Access Management (IAM) API v1-rev284-1.25.0 (from https://repo1.maven.org/maven2/com/google/apis/google-api-services-iam) - J2ObjC Annotations (from https://github.com/google/j2objc/) - JBoss Logging 3 (from http://www.jboss.org) +- JBoss Threads (from https://repo1.maven.org/maven2/org/jboss/threads/jboss-threads) - JCIP Annotations under Apache License (from http://stephenc.github.com/jcip-annotations) - JMES Path Query library (from https://aws.amazon.com/sdkforjava) -- JSON Small and Fast Parser (from http://www.minidev.net/) -- JSON Web Token support for the JVM (from https://github.com/jwtk/jjwt.git) +- JSON Small and Fast Parser (from https://repo1.maven.org/maven2/net/minidev/json-smart) +- JSON Web Token support for the JVM (from https://repo1.maven.org/maven2/io/jsonwebtoken/jjwt) - JSON library from Android SDK (from http://developer.android.com/sdk) - JSONassert (from https://github.com/skyscreamer/JSONassert) +- JSR107 API and SPI (from https://github.com/jsr107/jsr107spec) - Jackson (from http://jackson.codehaus.org) -- Jackson 2 extensions to the Google HTTP Client Library for Java. (from https://github.com/google/google-http-java-client.git/google-http-client-jackson2) -- Jackson dataformat: CBOR (from http://github.com/FasterXML/jackson-dataformats-binary) +- Jackson 2 extensions to the Google HTTP Client Library for Java. (from https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson2) - Jackson dataformat: CBOR (from http://github.com/FasterXML/jackson-dataformats-binary) -- Jackson datatype: JSR310 (from http://wiki.fasterxml.com/JacksonModuleJSR310) -- Jackson datatype: JSR310 (from http://wiki.fasterxml.com/JacksonModuleJSR310) -- Jackson extensions to the Google HTTP Client Library for Java. (from ) -- Jackson-annotations (from http://github.com/FasterXML/jackson) +- Jackson dataformat: Smile (from http://github.com/FasterXML/jackson-dataformats-binary) +- Jackson datatype: JSR310 (from https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jsr310) +- Jackson datatype: Joda (from https://github.com/FasterXML/jackson-datatype-joda) +- Jackson datatype: jdk8 (from https://repo1.maven.org/maven2/com/fasterxml/jackson/datatype/jackson-datatype-jdk8) +- Jackson extensions to the Google HTTP Client Library for Java. (from https://repo1.maven.org/maven2/com/google/http-client/google-http-client-jackson) +- Jackson module: Afterburner (from https://github.com/FasterXML/jackson-modules-base) +- Jackson module: JAXB Annotations (from https://github.com/FasterXML/jackson-modules-base) - Jackson-annotations (from http://github.com/FasterXML/jackson) - Jackson-core (from https://github.com/FasterXML/jackson-core) - Jackson-core (from https://github.com/FasterXML/jackson-core) -- Jackson-dataformat-Smile (from http://github.com/FasterXML/jackson-dataformat-smile) -- Jackson-dataformat-Smile (from http://github.com/FasterXML/jackson-dataformat-smile) - Jackson-dataformat-XML (from http://wiki.fasterxml.com/JacksonExtensionXmlDataBinding) - Jackson-dataformat-YAML (from https://github.com/FasterXML/jackson) -- Jackson-dataformat-YAML (from https://github.com/FasterXML/jackson) -- Jackson-datatype-Joda (from http://wiki.fasterxml.com/JacksonModuleJoda) -- Jackson-datatype-jdk8 (from ) -- Jackson-datatype-jdk8 (from ) -- Jackson-module-Afterburner (from http://wiki.fasterxml.com/JacksonHome) -- Jackson-module-JAXB-annotations (from http://wiki.fasterxml.com/JacksonJAXBAnnotations) -- Jackson-module-parameter-names (from ) -- Jackson-module-parameter-names (from ) +- Jackson-module-parameter-names (from https://repo1.maven.org/maven2/com/fasterxml/jackson/module/jackson-module-parameter-names) - Jakarta Bean Validation API (from https://beanvalidation.org) +- Jakarta Expression Language Implementation (from https://projects.eclipse.org/projects/ee4j.el) - Java Native Access (from https://github.com/java-native-access/jna) - Java Native Access Platform (from https://github.com/java-native-access/jna) +- Java Servlet 4.0 API (from ) +- Java Servlet 4.0 API (from ) - Java Servlet API (from https://projects.eclipse.org/projects/ee4j.servlet) - Java Servlet API (from http://servlet-spec.java.net) - Java UUID Generator (from http://wiki.fasterxml.com/JugHome) - Javassist (from http://www.javassist.org/) - Javassist (from http://www.javassist.org/) -- Joda-Time (from http://www.joda.org/joda-time/) -- Json Path (from https://github.com/jayway/JsonPath) +- JetBrains Java Annotations (from https://github.com/JetBrains/java-annotations) +- Joda-Time (from https://www.joda.org/joda-time/) - KeePassJava2 :: All (from https://repo1.maven.org/maven2/org/linguafranca/pwdb/KeePassJava2) - KeePassJava2 :: DOM (from https://repo1.maven.org/maven2/org/linguafranca/pwdb/KeePassJava2-dom) - KeePassJava2 :: JAXB (from https://repo1.maven.org/maven2/org/linguafranca/pwdb/KeePassJava2-jaxb) - KeePassJava2 :: KDB (from https://repo1.maven.org/maven2/org/linguafranca/pwdb/KeePassJava2-kdb) - KeePassJava2 :: KDBX (from https://repo1.maven.org/maven2/org/linguafranca/pwdb/KeePassJava2-kdbx) - KeePassJava2 :: Simple (from https://repo1.maven.org/maven2/org/linguafranca/pwdb/KeePassJava2-simple) -- Logback Contrib :: JSON :: Classic (from ) -- Logback Contrib :: JSON :: Core (from ) -- Logback Contrib :: Jackson (from ) -- Lucene Common Analyzers (from ) -- Lucene Core (from ) -- Lucene Grouping (from ) -- Lucene Highlighter (from ) -- Lucene Join (from ) -- Lucene Memory (from ) -- Lucene Memory (from ) -- Lucene Miscellaneous (from ) -- Lucene Queries (from ) -- Lucene QueryParsers (from ) -- Lucene Sandbox (from ) -- Lucene Spatial (from ) -- Lucene Spatial 3D (from ) -- Lucene Spatial Extras (from ) -- Lucene Suggest (from ) -- MapStruct Core (from ) -- Metrics Core (from https://github.com/dropwizard/metrics) -- Microsoft Application Insights Java SDK Core (from https://github.com/Microsoft/ApplicationInsights-Java) +- Logback Contrib :: JSON :: Classic (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-json-classic) +- Logback Contrib :: JSON :: Core (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-json-core) +- Logback Contrib :: Jackson (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-jackson) +- Lucene Common Analyzers (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-analyzers-common) +- Lucene Core (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-core) +- Lucene Grouping (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-grouping) +- Lucene Highlighter (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-highlighter) +- Lucene Join (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-join) +- Lucene Memory (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-backward-codecs) +- Lucene Memory (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-memory) +- Lucene Miscellaneous (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-misc) +- Lucene Queries (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-queries) +- Lucene QueryParsers (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-queryparser) +- Lucene Sandbox (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-sandbox) +- Lucene Spatial (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-spatial) +- Lucene Spatial 3D (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-spatial3d) +- Lucene Spatial Extras (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-spatial-extras) +- Lucene Suggest (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-suggest) +- MapStruct Core (from https://repo1.maven.org/maven2/org/mapstruct/mapstruct) +- Metrics Core (from https://repo1.maven.org/maven2/io/dropwizard/metrics/metrics-core) - Microsoft Application Insights Java SDK Spring Boot starter (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Java SDK Web Module (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Log4j 2 Appender (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Azure Java Core Library (from https://github.com/Azure/azure-sdk-for-java) - Microsoft Azure Netty HTTP Client Library (from https://github.com/Azure/azure-sdk-for-java) - Microsoft Azure SDK for SQL API of Azure Cosmos DB Service (from https://github.com/Azure/azure-sdk-for-java) -- Mockito (from http://mockito.org) - Mockito (from http://www.mockito.org) -- Netty Reactive Streams HTTP support (from ) -- Netty Reactive Streams Implementation (from ) -- Netty/Buffer (from http://netty.io/) -- Netty/Buffer (from http://netty.io/) -- Netty/Codec (from ) -- Netty/Codec (from ) -- Netty/Codec/HTTP (from ) -- Netty/Codec/HTTP (from ) -- Netty/Codec/HTTP2 (from ) -- Netty/Codec/HTTP2 (from ) -- Netty/Codec/Socks (from ) -- Netty/Common (from ) -- Netty/Common (from ) -- Netty/Handler (from ) -- Netty/Handler (from ) -- Netty/Handler/Proxy (from ) -- Netty/Resolver (from ) -- Netty/Resolver (from ) -- Netty/TomcatNative [BoringSSL - Static] (from ) -- Netty/Transport (from http://netty.io/) -- Netty/Transport (from http://netty.io/) -- Netty/Transport/Native/Unix/Common (from ) -- Netty/Transport/Native/Unix/Common (from ) -- Nimbus Content Type (from https://bitbucket.org/connect2id/nimbus-content-type) +- Netty/Buffer (from https://repo1.maven.org/maven2/io/netty/netty-buffer) +- Netty/Buffer (from https://repo1.maven.org/maven2/io/netty/netty-buffer) +- Netty/Codec (from https://repo1.maven.org/maven2/io/netty/netty-codec) +- Netty/Codec (from https://repo1.maven.org/maven2/io/netty/netty-codec) +- Netty/Codec/DNS (from https://repo1.maven.org/maven2/io/netty/netty-codec-dns) +- Netty/Codec/HTTP (from https://repo1.maven.org/maven2/io/netty/netty-codec-http) +- Netty/Codec/HTTP (from https://repo1.maven.org/maven2/io/netty/netty-codec-http) +- Netty/Codec/HTTP2 (from https://repo1.maven.org/maven2/io/netty/netty-codec-http2) +- Netty/Codec/HTTP2 (from https://repo1.maven.org/maven2/io/netty/netty-codec-http2) +- Netty/Codec/Socks (from https://repo1.maven.org/maven2/io/netty/netty-codec-socks) +- Netty/Common (from https://repo1.maven.org/maven2/io/netty/netty-common) +- Netty/Common (from https://repo1.maven.org/maven2/io/netty/netty-common) +- Netty/Handler (from https://repo1.maven.org/maven2/io/netty/netty-handler) +- Netty/Handler (from https://repo1.maven.org/maven2/io/netty/netty-handler) +- Netty/Handler/Proxy (from https://repo1.maven.org/maven2/io/netty/netty-handler-proxy) +- Netty/Resolver (from https://repo1.maven.org/maven2/io/netty/netty-resolver) +- Netty/Resolver (from https://repo1.maven.org/maven2/io/netty/netty-resolver) +- Netty/Resolver/DNS (from https://repo1.maven.org/maven2/io/netty/netty-resolver-dns) +- Netty/TomcatNative [BoringSSL - Static] (from https://repo1.maven.org/maven2/io/netty/netty-tcnative-boringssl-static) +- Netty/Transport (from https://repo1.maven.org/maven2/io/netty/netty-transport) +- Netty/Transport (from https://repo1.maven.org/maven2/io/netty/netty-transport) +- Netty/Transport/Native/Unix/Common (from https://repo1.maven.org/maven2/io/netty/netty-transport-native-unix-common) +- Netty/Transport/Native/Unix/Common (from https://repo1.maven.org/maven2/io/netty/netty-transport-native-unix-common) - Nimbus Content Type (from https://bitbucket.org/connect2id/nimbus-content-type) - Nimbus JOSE+JWT (from https://bitbucket.org/connect2id/nimbus-jose-jwt) - Nimbus JOSE+JWT (from https://bitbucket.org/connect2id/nimbus-jose-jwt) - Nimbus JOSE+JWT (from https://bitbucket.org/connect2id/nimbus-jose-jwt) - Nimbus LangTag (from https://bitbucket.org/connect2id/nimbus-language-tags) +- Nimbus LangTag (from https://bitbucket.org/connect2id/nimbus-language-tags) - Non-Blocking Reactive Foundation for the JVM (from https://github.com/reactor/reactor) - OAuth 2.0 SDK with OpenID Connect extensions (from https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions) - OAuth 2.0 SDK with OpenID Connect extensions (from https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions) - Objenesis (from http://objenesis.org) -- OkHttp (from ) -- OkHttp (from ) -- OkHttp Logging Interceptor (from ) -- OkHttp URLConnection (from ) -- OkHttp URLConnection (from ) -- Okio (from ) +- OkHttp (from https://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp) +- OkHttp (from https://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp) +- OkHttp Logging Interceptor (from https://repo1.maven.org/maven2/com/squareup/okhttp3/logging-interceptor) +- OpenCensus (from https://github.com/census-instrumentation/opencensus-java) - OpenCensus (from https://github.com/census-instrumentation/opencensus-java) - OpenCensus (from https://github.com/census-instrumentation/opencensus-java) - OpenCensus (from https://github.com/census-instrumentation/opencensus-java) - PWDB :: Database (from https://repo1.maven.org/maven2/org/linguafranca/pwdb/database) - PowerMock (from http://www.powermock.org) -- Protocol Buffer extensions to the Google HTTP Client Library for Java. (from ) +- PowerMock (from http://www.powermock.org) +- PowerMock (from http://www.powermock.org) +- PowerMock (from http://www.powermock.org) +- PowerMock (from http://www.powermock.org) +- PowerMock (from http://www.powermock.org) +- Protocol Buffer extensions to the Google HTTP Client Library for Java. (from https://repo1.maven.org/maven2/com/google/http-client/google-http-client-protobuf) +- Proton-J (from https://repo1.maven.org/maven2/org/apache/qpid/proton-j) - QpidJMS Client (from ) -- Reactive Streams Netty driver (from https://github.com/reactor/reactor-netty) -- Retrofit (from ) -- Simple XML (from http://simple.sourceforge.net) +- Reactor Netty with all modules (from https://github.com/reactor/reactor-netty) +- Retrofit (from https://repo1.maven.org/maven2/com/squareup/retrofit2/retrofit) - SnakeYAML (from http://www.snakeyaml.org) - Spring AOP (from https://github.com/spring-projects/spring-framework) - Spring Beans (from https://github.com/spring-projects/spring-framework) -- Spring Boot (from http://projects.spring.io/spring-boot/) -- Spring Boot Actuator (from http://projects.spring.io/spring-boot/) +- Spring Boot (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot) +- Spring Boot AOP Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-aop) +- Spring Boot Actuator (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-actuator) - Spring Boot Actuator AutoConfigure (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-actuator-autoconfigure) -- Spring Boot Actuator Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot AutoConfigure (from http://projects.spring.io/spring-boot/) -- Spring Boot Dependencies (from http://projects.spring.io/spring-boot/) +- Spring Boot Actuator Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-actuator) +- Spring Boot AutoConfigure (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-autoconfigure) - Spring Boot Json Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-json) -- Spring Boot Log4J2 Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Logging Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Security Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Test (from http://projects.spring.io/spring-boot/) -- Spring Boot Test Auto-Configure (from http://projects.spring.io/spring-boot/) -- Spring Boot Test Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Tomcat Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Undertow Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Undertow Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Validation Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Validation Starter (from http://projects.spring.io/spring-boot/) -- Spring Boot Web Starter (from http://projects.spring.io/spring-boot/) +- Spring Boot Logging Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-logging) +- Spring Boot Security Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-security) +- Spring Boot Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter) +- Spring Boot Test (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-test) +- Spring Boot Test Auto-Configure (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-test-autoconfigure) +- Spring Boot Test Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-test) +- Spring Boot Tomcat Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-tomcat) +- Spring Boot Validation Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-validation) +- Spring Boot Validation Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-validation) +- Spring Boot Web Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-web) +- Spring Boot Web Starter (from https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-web) - Spring Commons Logging Bridge (from https://github.com/spring-projects/spring-framework) - Spring Context (from https://github.com/spring-projects/spring-framework) - Spring Core (from https://github.com/spring-projects/spring-framework) -- Spring Data Core (from ) +- Spring Data Core (from https://repo1.maven.org/maven2/org/springframework/data/spring-data-commons) - Spring Expression Language (SpEL) (from https://github.com/spring-projects/spring-framework) - Spring JMS (from https://github.com/spring-projects/spring-framework) - Spring Messaging (from https://github.com/spring-projects/spring-framework) -- Spring Plugin - Core (from ) -- Spring Plugin - Metadata Extension (from ) +- Spring Plugin - Metadata Extension (from https://repo1.maven.org/maven2/org/springframework/plugin/spring-plugin-metadata) +- Spring Plugin Core (from https://repo1.maven.org/maven2/org/springframework/plugin/spring-plugin-core) - Spring TestContext Framework (from https://github.com/spring-projects/spring-framework) - Spring Transaction (from https://github.com/spring-projects/spring-framework) - Spring Web (from https://github.com/spring-projects/spring-framework) - Spring Web MVC (from https://github.com/spring-projects/spring-framework) -- StAX (from http://stax.codehaus.org/) -- StAX API (from http://stax.codehaus.org/) +- Spring WebFlux (from https://github.com/spring-projects/spring-framework) - T-Digest (from https://github.com/tdunning/t-digest) -- Undertow Core (from ) -- Undertow Core (from ) -- Undertow Servlet (from ) -- Undertow WebSockets JSR356 implementations (from ) +- Undertow Core (from https://repo1.maven.org/maven2/io/undertow/undertow-core) +- Undertow Core (from https://repo1.maven.org/maven2/io/undertow/undertow-core) +- Undertow Servlet (from https://repo1.maven.org/maven2/io/undertow/undertow-servlet) +- Undertow Servlet (from https://repo1.maven.org/maven2/io/undertow/undertow-servlet) +- Undertow WebSockets JSR356 implementations (from https://repo1.maven.org/maven2/io/undertow/undertow-websockets-jsr) +- Undertow WebSockets JSR356 implementations (from https://repo1.maven.org/maven2/io/undertow/undertow-websockets-jsr) +- Vavr (from http://vavr.io) +- Vavr Match (from http://vavr.io) +- WildFly Client Configuration (from ) - Woodstox (from https://github.com/FasterXML/woodstox) - XNIO API (from http://www.jboss.org/xnio) -- XNIO NIO Implementation (from ) +- XNIO API (from http://www.jboss.org/xnio) +- XNIO NIO Implementation (from https://repo1.maven.org/maven2/org/jboss/xnio/xnio-nio) +- XNIO NIO Implementation (from https://repo1.maven.org/maven2/org/jboss/xnio/xnio-nio) +- Zipkin Core Library (from https://repo1.maven.org/maven2/io/zipkin/zipkin2/zipkin) +- Zipkin Reporter Brave (from https://repo1.maven.org/maven2/io/zipkin/reporter2/zipkin-reporter-brave) +- Zipkin Reporter: Core (from https://repo1.maven.org/maven2/io/zipkin/reporter2/zipkin-reporter) - aalto-xml (from ) - aggs-matrix-stats (from https://github.com/elastic/elasticsearch) -- aws-ssm-java-caching-client (from https://github.com/awslabs/aws-ssm-java-caching-client) -- cli (from https://github.com/elastic/elasticsearch) -- com.google.api.grpc:grpc-google-cloud-pubsub-v1 (from https://github.com/googleapis/googleapis) -- com.google.api.grpc:proto-google-cloud-logging-v2 (from https://github.com/googleapis/googleapis) -- com.google.api.grpc:proto-google-cloud-pubsub-v1 (from https://github.com/googleapis/googleapis) -- com.google.api.grpc:proto-google-common-protos (from https://github.com/googleapis/googleapis) -- com.google.api.grpc:proto-google-iam-v1 (from https://github.com/googleapis/googleapis) +- asm (from http://asm.ow2.io/) - compiler (from http://github.com/spullara/mustache.java) -- datastore-v1-proto-client (from ) +- core (from https://github.com/elastic/elasticsearch) +- datastore-v1-proto-client (from https://repo1.maven.org/maven2/com/google/cloud/datastore/datastore-v1-proto-client) +- elasticsearch-cli (from https://github.com/elastic/elasticsearch) - elasticsearch-core (from https://github.com/elastic/elasticsearch) -- error-prone annotations (from ) -- error-prone annotations (from ) +- elasticsearch-secure-sm (from https://github.com/elastic/elasticsearch) +- elasticsearch-x-content (from https://github.com/elastic/elasticsearch) +- error-prone annotations (from https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations) +- error-prone annotations (from https://repo1.maven.org/maven2/com/google/errorprone/error_prone_annotations) +- grpc-google-cloud-pubsub-v1 (from https://repo1.maven.org/maven2/com/google/api/grpc/grpc-google-cloud-pubsub-v1) - io.grpc:grpc-alts (from https://github.com/grpc/grpc-java) - io.grpc:grpc-api (from https://github.com/grpc/grpc-java) - io.grpc:grpc-auth (from https://github.com/grpc/grpc-java) - io.grpc:grpc-context (from https://github.com/grpc/grpc-java) +- io.grpc:grpc-context (from https://github.com/grpc/grpc-java) +- io.grpc:grpc-core (from https://github.com/grpc/grpc-java) - io.grpc:grpc-core (from https://github.com/grpc/grpc-java) - io.grpc:grpc-grpclb (from https://github.com/grpc/grpc-java) - io.grpc:grpc-netty-shaded (from https://github.com/grpc/grpc-java) +- io.grpc:grpc-netty-shaded (from https://github.com/grpc/grpc-java) - io.grpc:grpc-protobuf (from https://github.com/grpc/grpc-java) +- io.grpc:grpc-protobuf (from https://github.com/grpc/grpc-java) +- io.grpc:grpc-protobuf-lite (from https://github.com/grpc/grpc-java) - io.grpc:grpc-protobuf-lite (from https://github.com/grpc/grpc-java) - io.grpc:grpc-stub (from https://github.com/grpc/grpc-java) +- io.grpc:grpc-stub (from https://github.com/grpc/grpc-java) - ion-java (from https://github.com/amznlabs/ion-java/) - ion-java (from https://github.com/amznlabs/ion-java/) - jackson-databind (from http://github.com/FasterXML/jackson) @@ -321,31 +313,56 @@ The following software have components provided under the terms of this license: - javax.inject (from http://code.google.com/p/atinject/) - jose4j (from https://bitbucket.org/b_c/jose4j/) - lang-mustache (from https://github.com/elastic/elasticsearch) -- lettuce (from http://github.com/mp911de/lettuce/wiki) +- lettuce (from https://github.com/lettuce-io/lettuce-core/wiki) - micrometer-core (from https://github.com/micrometer-metrics/micrometer) - micrometer-registry-azure-monitor (from https://github.com/micrometer-metrics/micrometer) +- mockito-core (from http://mockito.org) +- nio-multipart-parser (from ) +- nio-stream-storage (from https://github.com/synchronoss/nio-stream-storage) +- okhttp-urlconnection (from https://github.com/square/okhttp) +- okhttp-urlconnection (from https://github.com/square/okhttp) +- okio (from https://github.com/square/okio/) +- okio (from https://github.com/square/okio/) - org.apiguardian:apiguardian-api (from https://github.com/apiguardian-team/apiguardian) +- org.conscrypt:conscrypt-openjdk-uber (from https://conscrypt.org/) - org.opentest4j:opentest4j (from https://github.com/ota4j-team/opentest4j) - org.xmlunit:xmlunit-core (from http://www.xmlunit.org/) - parent-join (from https://github.com/elastic/elasticsearch) -- powermock-api-support (from ) -- powermock-core (from http://www.powermock.org) -- powermock-module-junit4 (from http://www.powermock.org) -- powermock-module-junit4-common (from ) -- powermock-reflect (from ) -- proto-google-cloud-datastore-v1 (from https://github.com/googleapis/api-client-staging) -- proton-j (from ) +- perfmark:perfmark-api (from https://github.com/perfmark/perfmark) +- project ':json-path' (from https://github.com/jayway/JsonPath) +- proto-google-cloud-datastore-v1 (from https://github.com/googleapis/java-datastore/proto-google-cloud-datastore-v1) +- proto-google-cloud-iamcredentials-v1 (from https://github.com/googleapis/java-iamcredentials/proto-google-cloud-iamcredentials-v1) +- proto-google-cloud-logging-v2 (from https://repo1.maven.org/maven2/com/google/api/grpc/proto-google-cloud-logging-v2) +- proto-google-cloud-pubsub-v1 (from https://github.com/googleapis/java-pubsub/proto-google-cloud-pubsub-v1) +- proto-google-common-protos (from https://github.com/googleapis/java-iam/proto-google-common-protos) +- proto-google-common-protos (from https://github.com/googleapis/java-iam/proto-google-common-protos) +- proto-google-iam-v1 (from https://github.com/googleapis/java-iam/proto-google-iam-v1) - rank-eval (from https://github.com/elastic/elasticsearch) +- resilience4j (from https://resilience4j.readme.io) +- resilience4j (from https://resilience4j.readme.io) +- resilience4j (from https://resilience4j.readme.io) +- resilience4j (from https://resilience4j.readme.io) +- resilience4j (from https://resilience4j.readme.io) +- resilience4j (from https://resilience4j.readme.io) +- resilience4j (from https://resilience4j.readme.io) +- resilience4j (from https://resilience4j.readme.io) - rest (from https://github.com/elastic/elasticsearch) - rest-high-level (from https://github.com/elastic/elasticsearch) - rxjava (from https://github.com/ReactiveX/RxJava) -- secure-sm (from https://github.com/elastic/elasticsearch) +- spring-boot-dependencies (from https://spring.io/projects/spring-boot) +- spring-boot-starter-log4j2 (from https://spring.io/projects/spring-boot) +- spring-boot-starter-reactor-netty (from https://spring.io/projects/spring-boot) +- spring-boot-starter-undertow (from https://spring.io/projects/spring-boot) +- spring-boot-starter-undertow (from https://spring.io/projects/spring-boot) +- spring-boot-starter-webflux (from https://spring.io/projects/spring-boot) +- spring-security-config (from http://spring.io/spring-security) - spring-security-config (from http://spring.io/spring-security) - spring-security-core (from http://spring.io/spring-security) - spring-security-oauth2-core (from http://spring.io/spring-security) - spring-security-oauth2-jose (from http://spring.io/spring-security) - spring-security-oauth2-resource-server (from http://spring.io/spring-security) - spring-security-web (from http://spring.io/spring-security) +- spring-security-web (from http://spring.io/spring-security) - springfox-core (from https://github.com/springfox/springfox) - springfox-schema (from https://github.com/springfox/springfox) - springfox-spi (from https://github.com/springfox/springfox) @@ -353,63 +370,59 @@ The following software have components provided under the terms of this license: - springfox-swagger-common (from https://github.com/springfox/springfox) - springfox-swagger-ui (from https://github.com/springfox/springfox) - springfox-swagger2 (from https://github.com/springfox/springfox) -- swagger-annotations (from ) -- swagger-jaxrs (from ) -- swagger-models (from ) +- swagger-annotations (from https://repo1.maven.org/maven2/io/swagger/swagger-annotations) +- swagger-jaxrs (from https://repo1.maven.org/maven2/io/swagger/swagger-jaxrs) +- swagger-models (from https://repo1.maven.org/maven2/io/swagger/swagger-models) - tomcat-embed-core (from http://tomcat.apache.org/) -- tomcat-embed-el (from http://tomcat.apache.org/) -- tomcat-embed-websocket (from http://tomcat.apache.org/) -- x-content (from https://github.com/elastic/elasticsearch) +- tomcat-embed-el (from https://tomcat.apache.org/) +- tomcat-embed-websocket (from https://tomcat.apache.org/) +- wildfly-common (from https://repo1.maven.org/maven2/org/wildfly/common/wildfly-common) ======================================================================== BSD-2-Clause ======================================================================== The following software have components provided under the terms of this license: -- API Common (from https://github.com/googleapis) -- GAX (Google Api eXtensions) (from https://github.com/googleapis) -- GAX (Google Api eXtensions) (from https://github.com/googleapis) -- GAX (Google Api eXtensions) (from https://github.com/googleapis) -- Hamcrest Core (from http://hamcrest.org/) -- Lucene Common Analyzers (from ) -- StAX (from http://stax.codehaus.org/) +- API Common (from https://github.com/googleapis/api-common-java) +- GAX (Google Api eXtensions) for Java (from https://github.com/googleapis/gax-java) +- GAX (Google Api eXtensions) for Java (from https://github.com/googleapis/gax-java) +- GAX (Google Api eXtensions) for Java (from https://github.com/googleapis/gax-java) +- Lucene Common Analyzers (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-analyzers-common) +- Lucene Core (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-core) +- Reflections (from http://github.com/ronmamo/reflections) - Stax2 API (from http://github.com/FasterXML/stax2-api) +- ThreeTen backport (from https://www.threeten.org/threetenbp) ======================================================================== BSD-3-Clause ======================================================================== The following software have components provided under the terms of this license: -- API Common (from https://github.com/googleapis) -- ASM Core (from ) -- Apache Commons Codec (from http://commons.apache.org/proper/commons-codec/) -- GAX (Google Api eXtensions) (from https://github.com/googleapis) -- GAX (Google Api eXtensions) (from https://github.com/googleapis) -- GAX (Google Api eXtensions) (from https://github.com/googleapis) -- Google APIs Client Library for Java (from ) -- Google Auth Library for Java - Credentials (from ) -- Google Auth Library for Java - OAuth2 HTTP (from ) -- Hamcrest library (from ) -- JavaBeans Activation Framework API jar (from ) -- Lucene Common Analyzers (from ) -- Lucene Core (from ) -- Lucene Suggest (from ) -- Microsoft Application Insights Java SDK Core (from https://github.com/Microsoft/ApplicationInsights-Java) +- API Common (from https://github.com/googleapis/api-common-java) +- Apache Commons Codec (from https://commons.apache.org/proper/commons-codec/) +- GAX (Google Api eXtensions) for Java (from https://github.com/googleapis/gax-java) +- GAX (Google Api eXtensions) for Java (from https://github.com/googleapis/gax-java) +- GAX (Google Api eXtensions) for Java (from https://github.com/googleapis/gax-java) +- Google APIs Client Library for Java (from https://repo1.maven.org/maven2/com/google/api-client/google-api-client) +- Google Auth Library for Java - Credentials (from https://repo1.maven.org/maven2/com/google/auth/google-auth-library-credentials) +- Google Auth Library for Java - OAuth2 HTTP (from https://repo1.maven.org/maven2/com/google/auth/google-auth-library-oauth2-http) +- Lucene Common Analyzers (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-analyzers-common) +- Lucene Core (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-core) +- Lucene Suggest (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-suggest) - Microsoft Application Insights Java SDK Spring Boot starter (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Java SDK Web Module (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Log4j 2 Appender (from https://github.com/Microsoft/ApplicationInsights-Java) - Mockito (from http://www.mockito.org) -- NanoHttpd-Core (from ) -- Netty/Codec/HTTP (from ) -- Netty/Codec/HTTP (from ) -- Protocol Buffer Java API (from https://developers.google.com/protocol-buffers/) -- Protocol Buffers [Util] (from ) +- Netty/Codec/HTTP (from https://repo1.maven.org/maven2/io/netty/netty-codec-http) +- Netty/Codec/HTTP (from https://repo1.maven.org/maven2/io/netty/netty-codec-http) +- Protocol Buffers [Core] (from https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java) +- Protocol Buffers [Core] (from https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java) +- Protocol Buffers [Util] (from https://repo1.maven.org/maven2/com/google/protobuf/protobuf-java-util) - Reflections (from http://github.com/ronmamo/reflections) - SnakeYAML (from http://www.snakeyaml.org) - Spring Core (from https://github.com/spring-projects/spring-framework) -- Stax2 API (from http://github.com/FasterXML/stax2-api) - ThreeTen backport (from https://www.threeten.org/threetenbp) -- jakarta.xml.bind-api (from ) +- asm (from http://asm.ow2.io/) ======================================================================== CC-BY-2.5 @@ -424,7 +437,6 @@ CC-BY-4.0 ======================================================================== The following software have components provided under the terms of this license: -- Microsoft Application Insights Java SDK Core (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Java SDK Spring Boot starter (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Java SDK Web Module (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Log4j 2 Appender (from https://github.com/Microsoft/ApplicationInsights-Java) @@ -434,9 +446,6 @@ CC0-1.0 ======================================================================== The following software have components provided under the terms of this license: -- XNIO API (from http://www.jboss.org/xnio) -- XNIO NIO Implementation (from ) -- reactive-streams (from http://www.reactive-streams.org/) - reactive-streams (from http://www.reactive-streams.org/) ======================================================================== @@ -444,25 +453,26 @@ CDDL-1.0 ======================================================================== The following software have components provided under the terms of this license: -- Common Annotations 1.2 API (from ) +- JAXB Reference Implementation (from http://jaxb.java.net/) - JavaBeans Activation Framework API jar (from ) -- Old JAXB Core (from ) -- Old JAXB Runtime (from ) -- javax.annotation-api (from http://jcp.org/en/jsr/detail?id=250) +- Old JAXB Core (from https://eclipse-ee4j.github.io/jaxb-ri/) ======================================================================== CDDL-1.1 ======================================================================== The following software have components provided under the terms of this license: +- Common Annotations 1.2 API (from ) - Expression Language 3.0 (from http://uel.java.net) -- Java Architecture For XML Binding (from ) -- Java Architecture For XML Binding (from ) +- Java Architecture for XML Binding (from http://jaxb.java.net/) +- Java Architecture for XML Binding (from http://jaxb.java.net/) +- Java Servlet 4.0 API (from ) - Java Servlet API (from http://servlet-spec.java.net) -- Java(TM) API for WebSocket (from ) +- Java(TM) API for WebSocket (from https://repo1.maven.org/maven2/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec) - JavaBeans Activation Framework (from ) - JavaBeans(TM) Activation Framework (from http://java.sun.com/javase/technologies/desktop/javabeans/jaf/index.jsp) -- JavaMail API (from ) +- JavaMail API (from https://repo1.maven.org/maven2/com/sun/mail/javax.mail) +- javax.annotation-api (from http://jcp.org/en/jsr/detail?id=250) - tomcat-embed-core (from http://tomcat.apache.org/) ======================================================================== @@ -472,43 +482,57 @@ The following software have components provided under the terms of this license: - JUnit (from http://junit.org) +======================================================================== +DOC +======================================================================== +The following software have components provided under the terms of this license: + +- Lucene Core (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-core) +- Woodstox (from https://github.com/FasterXML/woodstox) + ======================================================================== EPL-1.0 ======================================================================== The following software have components provided under the terms of this license: -- Expression Language 3.0 (from https://projects.eclipse.org/projects/ee4j.el) +- AspectJ Weaver (from https://www.eclipse.org/aspectj/) +- Common Annotations 1.3 API (from ) - JUnit Jupiter (Aggregator) (from https://junit.org/junit5/) +- JUnit Jupiter API (from https://junit.org/junit5/) +- JUnit Jupiter Engine (from https://junit.org/junit5/) +- JUnit Jupiter Params (from https://junit.org/junit5/) +- JUnit Platform Commons (from https://junit.org/junit5/) +- JUnit Platform Engine API (from https://junit.org/junit5/) +- Jakarta Expression Language Implementation (from https://projects.eclipse.org/projects/ee4j.el) +- Java Servlet 4.0 API (from ) - Java Servlet API (from https://projects.eclipse.org/projects/ee4j.servlet) -- Logback Classic Module (from ) -- Logback Contrib :: JSON :: Classic (from ) -- Logback Contrib :: JSON :: Core (from ) -- Logback Contrib :: Jackson (from ) -- Logback Core Module (from ) -- Microsoft Application Insights Java SDK Core (from https://github.com/Microsoft/ApplicationInsights-Java) +- Java(TM) API for WebSocket (from https://repo1.maven.org/maven2/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec) +- Logback Classic Module (from http://logback.qos.ch) +- Logback Contrib :: JSON :: Classic (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-json-classic) +- Logback Contrib :: JSON :: Core (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-json-core) +- Logback Contrib :: Jackson (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-jackson) +- Logback Core Module (from http://logback.qos.ch) - Microsoft Application Insights Java SDK Spring Boot starter (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Java SDK Web Module (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Log4j 2 Appender (from https://github.com/Microsoft/ApplicationInsights-Java) - SnakeYAML (from http://www.snakeyaml.org) -- org.junit.jupiter:junit-jupiter-api (from http://junit.org/junit5/) -- org.junit.jupiter:junit-jupiter-engine (from http://junit.org/junit5/) -- org.junit.jupiter:junit-jupiter-params (from http://junit.org/junit5/) -- org.junit.platform:junit-platform-commons (from http://junit.org/junit5/) -- org.junit.platform:junit-platform-engine (from http://junit.org/junit5/) ======================================================================== EPL-2.0 ======================================================================== The following software have components provided under the terms of this license: -- Expression Language 3.0 (from https://projects.eclipse.org/projects/ee4j.el) +- Common Annotations 1.3 API (from ) - JUnit Jupiter (Aggregator) (from https://junit.org/junit5/) +- JUnit Jupiter API (from https://junit.org/junit5/) +- JUnit Jupiter Engine (from https://junit.org/junit5/) +- JUnit Jupiter Params (from https://junit.org/junit5/) +- JUnit Platform Commons (from https://junit.org/junit5/) +- JUnit Platform Engine API (from https://junit.org/junit5/) +- Jakarta Expression Language Implementation (from https://projects.eclipse.org/projects/ee4j.el) +- Java Servlet 4.0 API (from ) - Java Servlet API (from https://projects.eclipse.org/projects/ee4j.servlet) -- org.junit.jupiter:junit-jupiter-api (from http://junit.org/junit5/) -- org.junit.jupiter:junit-jupiter-engine (from http://junit.org/junit5/) -- org.junit.jupiter:junit-jupiter-params (from http://junit.org/junit5/) -- org.junit.platform:junit-platform-commons (from http://junit.org/junit5/) -- org.junit.platform:junit-platform-engine (from http://junit.org/junit5/) +- Java(TM) API for WebSocket (from https://repo1.maven.org/maven2/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec) ======================================================================== GPL-2.0-only @@ -517,15 +541,14 @@ The following software have components provided under the terms of this license: - Common Annotations 1.2 API (from ) - Expression Language 3.0 (from http://uel.java.net) -- Java Architecture For XML Binding (from ) -- Java Architecture For XML Binding (from ) +- JAXB Reference Implementation (from http://jaxb.java.net/) +- Java Architecture for XML Binding (from http://jaxb.java.net/) +- Java Architecture for XML Binding (from http://jaxb.java.net/) +- Java Servlet 4.0 API (from ) - Java Servlet API (from http://servlet-spec.java.net) -- Java(TM) API for WebSocket (from ) +- Java(TM) API for WebSocket (from https://repo1.maven.org/maven2/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec) - JavaBeans Activation Framework (from ) -- JavaBeans Activation Framework API jar (from ) -- JavaMail API (from ) -- Old JAXB Core (from ) -- Old JAXB Runtime (from ) +- Old JAXB Core (from https://eclipse-ee4j.github.io/jaxb-ri/) - javax.annotation-api (from http://jcp.org/en/jsr/detail?id=250) - tomcat-embed-core (from http://tomcat.apache.org/) @@ -542,18 +565,20 @@ GPL-2.0-with-classpath-exception The following software have components provided under the terms of this license: - Checker Qual (from https://checkerframework.org) +- Common Annotations 1.3 API (from ) - Expression Language 3.0 (from http://uel.java.net) -- Expression Language 3.0 (from https://projects.eclipse.org/projects/ee4j.el) -- Java Architecture For XML Binding (from ) -- Java Architecture For XML Binding (from ) -- Java Servlet API (from https://projects.eclipse.org/projects/ee4j.servlet) +- JAXB Reference Implementation (from http://jaxb.java.net/) +- Jakarta Expression Language Implementation (from https://projects.eclipse.org/projects/ee4j.el) +- Java Architecture for XML Binding (from http://jaxb.java.net/) +- Java Architecture for XML Binding (from http://jaxb.java.net/) +- Java Servlet 4.0 API (from ) +- Java Servlet 4.0 API (from ) - Java Servlet API (from http://servlet-spec.java.net) -- Java(TM) API for WebSocket (from ) +- Java Servlet API (from https://projects.eclipse.org/projects/ee4j.servlet) +- Java(TM) API for WebSocket (from https://repo1.maven.org/maven2/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec) +- Java(TM) API for WebSocket (from https://repo1.maven.org/maven2/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec) - JavaBeans Activation Framework (from ) -- JavaBeans Activation Framework API jar (from ) -- JavaMail API (from ) -- Old JAXB Core (from ) -- Old JAXB Runtime (from ) +- Old JAXB Core (from https://eclipse-ee4j.github.io/jaxb-ri/) - javax.annotation-api (from http://jcp.org/en/jsr/detail?id=250) - tomcat-embed-core (from http://tomcat.apache.org/) @@ -562,13 +587,23 @@ GPL-3.0-only ======================================================================== The following software have components provided under the terms of this license: -- Expression Language 3.0 (from https://projects.eclipse.org/projects/ee4j.el) +- Common Annotations 1.3 API (from ) +- JAXB Reference Implementation (from http://jaxb.java.net/) +- Jakarta Expression Language Implementation (from https://projects.eclipse.org/projects/ee4j.el) +- Java Servlet 4.0 API (from ) +- Java Servlet 4.0 API (from ) - Java Servlet API (from https://projects.eclipse.org/projects/ee4j.servlet) -- Java(TM) API for WebSocket (from ) -- Old JAXB Core (from ) -- Old JAXB Runtime (from ) +- Java(TM) API for WebSocket (from https://repo1.maven.org/maven2/org/jboss/spec/javax/websocket/jboss-websocket-api_1.1_spec) +- Old JAXB Core (from https://eclipse-ee4j.github.io/jaxb-ri/) - Project Lombok (from https://projectlombok.org) -- SnakeYAML (from http://www.snakeyaml.org) + +======================================================================== +ImageMagick +======================================================================== +The following software have components provided under the terms of this license: + +- Stax2 API (from http://github.com/FasterXML/stax2-api) +- Woodstox (from https://github.com/FasterXML/woodstox) ======================================================================== JSON @@ -582,28 +617,27 @@ LGPL-2.1-only ======================================================================== The following software have components provided under the terms of this license: -- Elastic JNA Distribution (from https://github.com/java-native-access/jna) +- Elastic JNA Distribution (from https://github.com/elastic/jna-build) - Java Native Access (from https://github.com/java-native-access/jna) - Java Native Access Platform (from https://github.com/java-native-access/jna) - Javassist (from http://www.javassist.org/) - Javassist (from http://www.javassist.org/) -- Logback Classic Module (from ) -- Logback Contrib :: JSON :: Classic (from ) -- Logback Contrib :: JSON :: Core (from ) -- Logback Contrib :: Jackson (from ) -- Logback Core Module (from ) -- Microsoft Application Insights Java SDK Core (from https://github.com/Microsoft/ApplicationInsights-Java) +- Logback Classic Module (from http://logback.qos.ch) +- Logback Contrib :: JSON :: Classic (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-json-classic) +- Logback Contrib :: JSON :: Core (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-json-core) +- Logback Contrib :: Jackson (from https://repo1.maven.org/maven2/ch/qos/logback/contrib/logback-jackson) +- Logback Core Module (from http://logback.qos.ch) - Microsoft Application Insights Java SDK Spring Boot starter (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Java SDK Web Module (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Log4j 2 Appender (from https://github.com/Microsoft/ApplicationInsights-Java) -- XNIO API (from http://www.jboss.org/xnio) ======================================================================== LGPL-2.1-or-later ======================================================================== The following software have components provided under the terms of this license: -- Java Native Access Platform (from https://github.com/java-native-access/jna) +- JBoss Threads (from https://repo1.maven.org/maven2/org/jboss/threads/jboss-threads) +- Javassist (from http://www.javassist.org/) - Javassist (from http://www.javassist.org/) - SnakeYAML (from http://www.snakeyaml.org) @@ -612,8 +646,8 @@ LGPL-3.0-only ======================================================================== The following software have components provided under the terms of this license: -- Apache Log4j API (from ) -- Apache Log4j Core (from ) +- Apache Log4j API (from https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-api) +- Apache Log4j Core (from https://repo1.maven.org/maven2/org/apache/logging/log4j/log4j-core) ======================================================================== MIT @@ -621,22 +655,20 @@ MIT The following software have components provided under the terms of this license: - AWS Java SDK for AWS Lambda (from https://aws.amazon.com/sdkforjava) -- Animal Sniffer Annotations (from ) -- Azure AD Spring Security Integration Spring Boot Starter (from https://github.com/Microsoft/azure-spring-boot) +- Animal Sniffer Annotations (from https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations) +- Animal Sniffer Annotations (from https://repo1.maven.org/maven2/org/codehaus/mojo/animal-sniffer-annotations) - Azure Java Client Authentication Library for AutoRest (from https://github.com/Azure/autorest-clientruntime-for-java) - Azure Java Client Runtime for ARM (from https://github.com/Azure/autorest-clientruntime-for-java) - Azure Java Client Runtime for AutoRest (from https://github.com/Azure/autorest-clientruntime-for-java) -- Azure Metrics Spring Boot Starter (from https://github.com/Microsoft/azure-spring-boot) -- Azure Spring Boot AutoConfigure (from https://github.com/Microsoft/azure-spring-boot) +- Azure Spring Boot AutoConfigure (from https://github.com/Azure/azure-sdk-for-java) +- Checker Qual (from https://checkerframework.org) - Checker Qual (from https://checkerframework.org) - Checker Qual (from https://checkerframework.org) - Extensions on Apache Proton-J library (from https://github.com/Azure/qpid-proton-j-extensions) -- JOpt Simple (from http://pholser.github.io/jopt-simple) +- JOpt Simple (from http://pholser.github.com/jopt-simple) - JUL to SLF4J bridge (from http://www.slf4j.org) - Java Client Runtime for AutoRest (from https://github.com/Azure/autorest-clientruntime-for-java) -- Java JWT (from http://www.jwt.io) -- Java JWT (from http://www.jwt.io) -- Lucene Core (from ) +- Lucene Core (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-core) - Microsoft Application Insights Java SDK Core (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Java SDK Spring Boot starter (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Application Insights Java SDK Web Module (from https://github.com/Microsoft/ApplicationInsights-Java) @@ -655,11 +687,10 @@ The following software have components provided under the terms of this license: - Microsoft Azure common module for Storage (from https://github.com/Azure/azure-sdk-for-java) - Microsoft Azure internal Avro module for Storage (from https://github.com/Azure/azure-sdk-for-java) - Mockito (from http://www.mockito.org) -- Mockito (from http://mockito.org) -- Netty/Codec/HTTP (from ) -- Netty/Codec/HTTP (from ) -- Netty/Common (from ) -- Netty/Common (from ) +- Netty/Codec/HTTP (from https://repo1.maven.org/maven2/io/netty/netty-codec-http) +- Netty/Codec/HTTP (from https://repo1.maven.org/maven2/io/netty/netty-codec-http) +- Netty/Common (from https://repo1.maven.org/maven2/io/netty/netty-common) +- Netty/Common (from https://repo1.maven.org/maven2/io/netty/netty-common) - Project Lombok (from https://projectlombok.org) - SLF4J API Module (from http://www.slf4j.org) - Spongy Castle (from http://rtyley.github.io/spongycastle/) @@ -667,6 +698,10 @@ The following software have components provided under the terms of this license: - adal4j (from https://github.com/AzureAD/azure-activedirectory-library-for-java) - azure-documentdb (from https://azure.microsoft.com/en-us/services/cosmos-db/) - documentdb-bulkexecutor (from http://azure.microsoft.com/en-us/services/documentdb/) +- java jwt (from https://github.com/auth0/java-jwt) +- java jwt (from https://github.com/auth0/java-jwt) +- micrometer-core (from https://github.com/micrometer-metrics/micrometer) +- mockito-core (from http://mockito.org) - mockito-junit-jupiter (from https://github.com/mockito/mockito) - msal4j (from https://github.com/AzureAD/microsoft-authentication-library-for-java) - msal4j-persistence-extension (from https://github.com/AzureAD/microsoft-authentication-extensions-for-java) @@ -687,28 +722,23 @@ The following software have components provided under the terms of this license: - Javassist (from http://www.javassist.org/) - Javassist (from http://www.javassist.org/) +- OkHttp (from https://repo1.maven.org/maven2/com/squareup/okhttp3/okhttp) ======================================================================== -PHP-3.01 +MS-RL ======================================================================== The following software have components provided under the terms of this license: -- JavaBeans Activation Framework API jar (from ) -- jakarta.xml.bind-api (from ) +- Microsoft Application Insights Java SDK Core (from https://github.com/Microsoft/ApplicationInsights-Java) ======================================================================== Public-Domain ======================================================================== The following software have components provided under the terms of this license: -- HdrHistogram (from http://hdrhistogram.github.io/HdrHistogram/) -- LatencyUtils (from http://latencyutils.github.io/LatencyUtils/) -- Old JAXB Core (from ) -- Old JAXB Runtime (from ) +- JAXB Reference Implementation (from http://jaxb.java.net/) +- Old JAXB Core (from https://eclipse-ee4j.github.io/jaxb-ri/) - Spongy Castle (from http://rtyley.github.io/spongycastle/) -- XNIO API (from http://www.jboss.org/xnio) -- XNIO NIO Implementation (from ) -- reactive-streams (from http://www.reactive-streams.org/) ======================================================================== SPL-1.0 @@ -718,6 +748,13 @@ The following software have components provided under the terms of this license: - Checker Qual (from https://checkerframework.org) - Checker Qual (from https://checkerframework.org) +======================================================================== +SunPro +======================================================================== +The following software have components provided under the terms of this license: + +- Lucene Core (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-core) + ======================================================================== WTFPL ======================================================================== @@ -725,17 +762,23 @@ The following software have components provided under the terms of this license: - Reflections (from http://github.com/ronmamo/reflections) +======================================================================== +X11 +======================================================================== +The following software have components provided under the terms of this license: + +- Lucene Core (from https://repo1.maven.org/maven2/org/apache/lucene/lucene-core) + ======================================================================== public-domain ======================================================================== The following software have components provided under the terms of this license: -- AWS Java SDK :: SDK Core (from https://aws.amazon.com/sdkforjava) -- Asynchronous Http Client (from ) -- Guava: Google Core Libraries for Java (from https://github.com/google/guava.git) -- Guava: Google Core Libraries for Java (from https://github.com/google/guava.git) +- Asynchronous Http Client (from https://repo1.maven.org/maven2/org/asynchttpclient/async-http-client) +- Guava: Google Core Libraries for Java (from https://github.com/google/guava) +- Guava: Google Core Libraries for Java (from https://github.com/google/guava) - HdrHistogram (from http://hdrhistogram.github.io/HdrHistogram/) -- Joda-Time (from http://www.joda.org/joda-time/) +- Joda-Time (from https://www.joda.org/joda-time/) - LatencyUtils (from http://latencyutils.github.io/LatencyUtils/) - Microsoft Application Insights Java SDK Core (from https://github.com/Microsoft/ApplicationInsights-Java) - Microsoft Azure SDK for EventGrid Management (from https://github.com/Azure/azure-sdk-for-java) @@ -743,7 +786,6 @@ The following software have components provided under the terms of this license: - Microsoft Azure client library for Blob Storage (from https://github.com/Azure/azure-sdk-for-java) - Project Lombok (from https://projectlombok.org) - Spring Web (from https://github.com/spring-projects/spring-framework) -- StAX API (from http://stax.codehaus.org/) - azure-documentdb (from https://azure.microsoft.com/en-us/services/cosmos-db/) - msal4j (from https://github.com/AzureAD/microsoft-authentication-library-for-java) - reactive-streams (from http://www.reactive-streams.org/) @@ -753,17 +795,16 @@ unknown ======================================================================== The following software have components provided under the terms of this license: -- Byte Buddy (without dependencies) (from ) -- Common Annotations 1.2 API (from ) +- Byte Buddy (without dependencies) (from https://repo1.maven.org/maven2/net/bytebuddy/byte-buddy) +- Checker Qual (from https://checkerframework.org) +- JSON in Java (from https://github.com/douglascrockford/JSON-java) - JUnit (from http://junit.org) - JUnit Jupiter (Aggregator) (from https://junit.org/junit5/) -- JavaBeans Activation Framework API jar (from ) +- JUnit Jupiter API (from https://junit.org/junit5/) +- JUnit Jupiter Engine (from https://junit.org/junit5/) +- JUnit Jupiter Params (from https://junit.org/junit5/) +- JUnit Platform Commons (from https://junit.org/junit5/) +- JUnit Platform Engine API (from https://junit.org/junit5/) - Spongy Castle (from http://rtyley.github.io/spongycastle/) -- jakarta.xml.bind-api (from ) -- org.junit.jupiter:junit-jupiter-api (from http://junit.org/junit5/) -- org.junit.jupiter:junit-jupiter-engine (from http://junit.org/junit5/) -- org.junit.jupiter:junit-jupiter-params (from http://junit.org/junit5/) -- org.junit.platform:junit-platform-commons (from http://junit.org/junit5/) -- org.junit.platform:junit-platform-engine (from http://junit.org/junit5/) diff --git a/devops/azure/chart/helm-config.yaml b/devops/azure/chart/helm-config.yaml index 9323d6f1afcf141b010c15d465491515ba8de25c..325ceab14ff42e7390ee5eba6e9982d33d5d6ec8 100644 --- a/devops/azure/chart/helm-config.yaml +++ b/devops/azure/chart/helm-config.yaml @@ -17,6 +17,8 @@ global: # Service(s) Replica Count replicaCount: 1 + nodepool: services + isAutoscalingEnabled: false ################################################################################ # Specify the Gitlab branch being used for image creation @@ -26,3 +28,5 @@ image: repository: #{container-registry}#.azurecr.io branch: #{ENVIRONMENT_NAME}# tag: #{Build.SourceVersion}# + +istioDnsHost: #{ISTIO_DNS_HOST}# \ No newline at end of file diff --git a/devops/azure/chart/templates/authSB.yaml b/devops/azure/chart/templates/authSB.yaml new file mode 100644 index 0000000000000000000000000000000000000000..50e54fb1281dc4c6759c1d4327b16325ffbcb258 --- /dev/null +++ b/devops/azure/chart/templates/authSB.yaml @@ -0,0 +1,33 @@ +# Source: istio/templates/notification.yaml +# Copyright © Microsoft Corporation +# +# 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. + +apiVersion: security.istio.io/v1beta1 +kind: AuthorizationPolicy +metadata: + name: notification-sb-jwt-authz + namespace: osdu +spec: + selector: + matchLabels: + app: notification-sb + action: DENY + rules: + - from: + - source: + notRequestPrincipals: ["*"] + to: + - operation: + notPaths: ["/","*/swagger-resources","*/swagger", + "/api/notification/v1/swagger-resources/*","*/swagger-ui.html","*/actuator/health"] \ No newline at end of file diff --git a/devops/azure/chart/templates/deployment.yaml b/devops/azure/chart/templates/deployment.yaml index e34555ef94f284c34bad1677db94762323afb2e8..a6f9651b77df6cc74a70074ced49168103aaca52 100644 --- a/devops/azure/chart/templates/deployment.yaml +++ b/devops/azure/chart/templates/deployment.yaml @@ -28,6 +28,10 @@ spec: app: {{ .Chart.Name }} aadpodidbinding: osdu-identity spec: +{{- if .Values.global.isAutoscalingEnabled }} + nodeSelector: + nodepool: {{ .Values.global.nodepool }} +{{- end }} volumes: - name: azure-keyvault csi: @@ -58,6 +62,8 @@ spec: value: /api/notification/v1 - name: server_port value: "80" + - name: notification_spring_logging_level + value: INFO - name: KEYVAULT_URI valueFrom: configMapKeyRef: @@ -77,9 +83,27 @@ spec: value: osdu-db - name: entitlements_service_endpoint value: http://entitlements/api/entitlements/v2 + - name: entitlements_service_api_key + value: "OBSOLETE" - name: registeration_service_endpoint value: http://register/api/register/v1 - name: partition_service_endpoint value: http://partition/api/partition/v1 - name: maxCacheSize - value: "20" \ No newline at end of file + value: "20" + - name: max_concurrent_calls + value: "3" + - name: executor_n_threads + value: "32" + - name: max_lock_renew_duration_seconds + value: "2000" + - name: initial_subscription_manager_delay_seconds + value: "0" + - name: consecutive_subscription_manager_delay_seconds + value: "1800" + - name: service_bus_enabled + value: "false" + - name: event_grid_to_service_bus_enabled + value: "false" + - name: event_grid_enabled + value: "true" \ No newline at end of file diff --git a/devops/azure/chart/templates/deploymentSB.yaml b/devops/azure/chart/templates/deploymentSB.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e254ef27454ef8ef09ddec45304a26da3ba0e46f --- /dev/null +++ b/devops/azure/chart/templates/deploymentSB.yaml @@ -0,0 +1,105 @@ +# Copyright © Microsoft Corporation +# +# 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. + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-sb + namespace: osdu +spec: + replicas: {{ .Values.global.replicaCount }} + selector: + matchLabels: + app: {{ .Chart.Name }}-sb + template: + metadata: + labels: + app: {{ .Chart.Name }}-sb + aadpodidbinding: osdu-identity + spec: + volumes: + - name: azure-keyvault + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: azure-keyvault + containers: + - name: {{ .Chart.Name }}-sb + image: {{ .Values.image.repository }}/{{ .Chart.Name }}-{{ .Values.image.branch }}:{{ .Values.image.tag | default .Chart.AppVersion }} + imagePullPolicy: Always + ports: + - containerPort: 81 + readinessProbe: + httpGet: + path: /api/notification/v1/swagger-ui.html + port: 81 + volumeMounts: + - name: azure-keyvault + mountPath: "/mnt/azure-keyvault" + readOnly: true + env: + - name: spring_application_name + value: notification-sb-azure + - name: LOG_PREFIX + value: "notification-sb" + - name: server.servlet.contextPath + value: /api/notification/v1 + - name: server_port + value: "81" + - name: notification_spring_logging_level + value: INFO + - name: KEYVAULT_URI + valueFrom: + configMapKeyRef: + name: osdu-svc-properties + key: ENV_KEYVAULT + - name: aad_client_id + valueFrom: + secretKeyRef: + name: active-directory + key: application-appid + - name: appinsights_key + valueFrom: + secretKeyRef: + name: central-logging + key: appinsights + - name: cosmosdb_database + value: osdu-db + - name: entitlements_service_endpoint + value: http://entitlements/api/entitlements/v2 + - name: entitlements_service_api_key + value: "OBSOLETE" + - name: registeration_service_endpoint + value: http://register/api/register/v1 + - name: partition_service_endpoint + value: http://partition/api/partition/v1 + - name: maxCacheSize + value: "20" + - name: max_concurrent_calls + value: "3" + - name: executor_n_threads + value: "32" + - name: max_lock_renew_duration_seconds + value: "500" + - name: initial_subscription_manager_delay_seconds + value: "0" + - name: consecutive_subscription_manager_delay_seconds + value: "600" + - name: service_bus_enabled + value: "true" + - name: event_grid_to_service_bus_enabled + value: "false" + - name: event_grid_enabled + value: "false" \ No newline at end of file diff --git a/devops/azure/chart/templates/serviceSB.yaml b/devops/azure/chart/templates/serviceSB.yaml new file mode 100644 index 0000000000000000000000000000000000000000..616d3e28c07a0ef0695c940f903d61b0454ba25b --- /dev/null +++ b/devops/azure/chart/templates/serviceSB.yaml @@ -0,0 +1,27 @@ +# Copyright © Microsoft Corporation +# +# 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. + +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }}-sb + namespace: osdu +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 81 + targetPort: 81 + selector: + app: {{ .Chart.Name }}-sb \ No newline at end of file diff --git a/devops/azure/chart/templates/virtual-service.yaml b/devops/azure/chart/templates/virtual-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..8229d11265b27a3e60df969de6ee8d0b04315bc4 --- /dev/null +++ b/devops/azure/chart/templates/virtual-service.yaml @@ -0,0 +1,37 @@ +--- +# Source: /devops/azure/chart/templates/virtual-service.yaml +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: {{ .Chart.Name }} + namespace: osdu +spec: + hosts: + - "{{ .Values.istioDnsHost }}" + gateways: + - istio-gateway + http: + - match: + - uri: + prefix: "/api/{{ .Chart.Name }}/v1" + route: + - destination: + host: {{ .Chart.Name }} + port: + number: 80 + corsPolicy: + maxAge: "60m" + allowCredentials: true + allowHeaders: + - Authorization + - Data-Partition-Id + - Correlation-Id + - Content-Type + allowMethods: + - POST + - GET + - PUT + - PATCH + - DELETE + allowOrigins: + - prefix: "*" \ No newline at end of file diff --git a/devops/azure/chart/values.yaml b/devops/azure/chart/values.yaml index d503dd73deaa4d89c63c0bc034cac4354c4970b2..04027457beffc4da0c986211f30c669a348a291a 100644 --- a/devops/azure/chart/values.yaml +++ b/devops/azure/chart/values.yaml @@ -14,8 +14,12 @@ global: replicaCount: 1 + nodepool: services + isAutoscalingEnabled: false image: repository: community.opengroup.org:5555/osdu/platform/system/notification branch: master - tag: latest \ No newline at end of file + tag: latest + +istioDnsHost: "" \ No newline at end of file diff --git a/devops/azure/development-pipeline.yml b/devops/azure/development-pipeline.yml index 532aa11e8b05cca1b058dc0b25f68efca0f6c51d..eca20d42ae0d6678d4b2b21ff820a58657982be8 100644 --- a/devops/azure/development-pipeline.yml +++ b/devops/azure/development-pipeline.yml @@ -57,8 +57,8 @@ stages: parameters: mavenGoal: 'package' mavenPublishJUnitResults: true - serviceCoreMavenOptions: '-P notification-core --settings .mvn/community-maven.settings.xml' - mavenOptions: '-P notification-azure --settings .mvn/community-maven.settings.xml -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)' + serviceCoreMavenOptions: '-pl notification-core --settings .mvn/community-maven.settings.xml -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)' + mavenOptions: '-pl provider/notification-azure --settings .mvn/community-maven.settings.xml -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)' copyFileContents: | pom.xml provider/notification-azure/maven/settings.xml diff --git a/devops/azure/pipeline.yml b/devops/azure/pipeline.yml index 13617a7552be024dd921f877b6d371190c2a6c8c..f5c86d139932a3f7c90e5aa8e632f67b81d3b2e2 100644 --- a/devops/azure/pipeline.yml +++ b/devops/azure/pipeline.yml @@ -57,8 +57,9 @@ stages: parameters: mavenGoal: 'package' mavenPublishJUnitResults: true - serviceCoreMavenOptions: '-P notification-core --settings .mvn/community-maven.settings.xml' - mavenOptions: '-P notification-azure --settings .mvn/community-maven.settings.xml -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)' + serviceCoreMavenOptions: '-pl notification-core --settings .mvn/community-maven.settings.xml -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)' + mavenOptions: '-pl provider/notification-azure --settings .mvn/community-maven.settings.xml -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)' + copyFileContents: | pom.xml provider/notification-azure/maven/settings.xml diff --git a/devops/gcp/configmap/Chart.yaml b/devops/gcp/configmap/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..601fcf729aeb8cd65be3bc6aec5a08d15c4a8dc0 --- /dev/null +++ b/devops/gcp/configmap/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: gcp-notification-configmap +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/devops/gcp/configmap/templates/notification-configmap.yaml b/devops/gcp/configmap/templates/notification-configmap.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1c1ce1b79705a62cb36a1ab3db469f0b7e2347fc --- /dev/null +++ b/devops/gcp/configmap/templates/notification-configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app: "{{ .Values.conf.app_name }}" + name: "{{ .Values.conf.configmap }}" + namespace: "{{ .Release.Namespace }}" +data: + LOG_LEVEL: "{{ .Values.data.log_level }}" + APP_PROJECT: "{{ .Values.data.app_project }}" + APP_ENTITLEMENTS: "{{ .Values.data.app_entitlements }}" + APP_REGISTER: "{{ .Values.data.app_register }}" + APP_GOOGLEAUDIENCE: "{{ .Values.data.app_googleaudience }}" + PARTITION_API: "{{ .Values.data.partition_api }}" + GOOGLE_AUDIENCES: "{{ .Values.data.google_audiences }}" diff --git a/devops/gcp/configmap/values.yaml b/devops/gcp/configmap/values.yaml new file mode 100644 index 0000000000000000000000000000000000000000..36510b00e6b14022d3a4b1daf3b376c700bb9752 --- /dev/null +++ b/devops/gcp/configmap/values.yaml @@ -0,0 +1,11 @@ +data: + log_level: "INFO" + app_project: "" + app_entitlements: "http://entitlements/api/entitlements/v2/" + app_register: "http://register/api/register/v1" + app_googleaudience: "" + partition_api: "http://partition/api/partition/v1/" + google_audiences: "" +conf: + configmap: "notification-config" + app_name: "notification" diff --git a/devops/gcp/deploy/Chart.yaml b/devops/gcp/deploy/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ef8338da0cf56eaadbf1aaee05d2a3fdcff24f1c --- /dev/null +++ b/devops/gcp/deploy/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: gcp-notification-deploy +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/devops/gcp/deploy/templates/deployment.yaml b/devops/gcp/deploy/templates/deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b3d3b4cc336c616f9d904da01f2a45dd99bf6105 --- /dev/null +++ b/devops/gcp/deploy/templates/deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: "{{ .Values.conf.app_name }}" + name: "{{ .Values.conf.app_name }}" + namespace: "{{ .Release.Namespace }}" +spec: + selector: + matchLabels: + app: "{{ .Values.conf.app_name }}" + replicas: 1 + template: + metadata: + labels: + app: "{{ .Values.conf.app_name }}" + annotations: + rollme: {{ randAlphaNum 5 | quote }} + spec: + containers: + - name: "{{ .Values.conf.app_name }}" + image: "{{ .Values.data.image }}" + envFrom: + - configMapRef: + name: "{{ .Values.conf.configmap }}" + securityContext: + allowPrivilegeEscalation: false + runAsUser: 0 + ports: + - containerPort: 8080 + resources: + requests: + cpu: "{{ .Values.data.requests_cpu }}" + memory: "{{ .Values.data.requests_memory }}" + limits: + cpu: "{{ .Values.data.limits_cpu }}" + memory: "{{ .Values.data.limits_memory }}" + serviceAccountName: "{{ .Values.data.serviceAccountName }}" diff --git a/devops/gcp/deploy/templates/service.yaml b/devops/gcp/deploy/templates/service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d92e1b6fb4b7890067aaf4d6b46c8310162d4d6e --- /dev/null +++ b/devops/gcp/deploy/templates/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ .Values.conf.app_name }}" + annotations: + cloud.google.com/neg: '{"ingress": true}' + namespace: "{{ .Release.Namespace }}" + labels: + app: "{{ .Values.conf.app_name }}" + service: "{{ .Values.conf.app_name }}" +spec: + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + name: http + selector: + app: "{{ .Values.conf.app_name }}" diff --git a/devops/gcp/deploy/templates/virtual-service.yaml b/devops/gcp/deploy/templates/virtual-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ba166b2c2e6511f89b1bd16d296944a32d656f24 --- /dev/null +++ b/devops/gcp/deploy/templates/virtual-service.yaml @@ -0,0 +1,19 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: "{{ .Values.conf.app_name }}" + namespace: "{{ .Release.Namespace }}" +spec: + hosts: + - "*" + gateways: + - service-gateway + http: + - match: + - uri: + prefix: "/api/notification" + route: + - destination: + port: + number: 80 + host: "{{ .Values.conf.app_name }}.{{ .Release.Namespace }}.svc.cluster.local" diff --git a/devops/gcp/deploy/values.yaml b/devops/gcp/deploy/values.yaml new file mode 100644 index 0000000000000000000000000000000000000000..85ec3f779968f9308d3b5435e3c4788ae3b20036 --- /dev/null +++ b/devops/gcp/deploy/values.yaml @@ -0,0 +1,11 @@ +data: + requests_cpu: "0.25" + requests_memory: "256M" + limits_cpu: "1" + limits_memory: "1G" + serviceAccountName: "" + image: "" + +conf: + configmap: "notification-config" + app_name: "notification" diff --git a/docs/api/notification_openapi.yaml b/docs/api/notification_openapi.yaml index e65325415809b7d5d4740c30e7d8eb53042c58da..f4ec41d72dd53d55d9b03d29cf4e579b6c5a98d8 100644 --- a/docs/api/notification_openapi.yaml +++ b/docs/api/notification_openapi.yaml @@ -12,6 +12,8 @@ info: tags: - name: pubsub-endpoint description: Pubsub Endpoint + - name: info + description: Version info endpoint paths: /push-handlers/records-changed: post: @@ -45,6 +47,21 @@ paths: security: - JWT: - global + /info: + get: + tags: + - info + summary: "Version info" + description: "For deployment available public `/info` endpoint, \ + \ which provides build and git related information." + operationId: "Version info" + produces: + - "application/json" + responses: + 200: + description: "Version info." + schema: + $ref: "#/components/schemas/VersionInfo" servers: - url: https://evq.csp.osdu.com/api/notification/v1 description: EVT @@ -66,4 +83,44 @@ components: type: string statusCodeValue: type: integer - format: int32 \ No newline at end of file + format: int32 + VersionInfo: + type: "object" + properties: + groupId: + type: "string" + description: "Maven artifact group ID." + actifactId: + type: "string" + description: "Maven artifact ID." + version: + type: "string" + description: "Maven artifact version" + buildTime: + type: "string" + description: "Maven artifact build time" + branch: + type: "string" + description: "Current git branch" + commitId: + type: "string" + description: "Latest commit hash" + commitMessage: + type: "string" + description: "Latest commit message" + connectedOuterServices: + type: "array" + description: "Connected outer services information" + items: + $ref: "#/components/schemas/ConnectedOuterService" + description: "Version info." + ConnectedOuterService: + type: "object" + properties: + name: + type: "string" + description: "Connected outer service name." + version: + type: "string" + description: "Connected outer service version." + description: "Connected outer service information." \ No newline at end of file diff --git a/docs/tutorial/DataNotification.md b/docs/tutorial/DataNotification.md index 93cc3a0d6f184626e6eeafcc77e218ea08a17945..a590cc37baf27a67299eb82db132b8309ea56e40 100644 --- a/docs/tutorial/DataNotification.md +++ b/docs/tutorial/DataNotification.md @@ -11,6 +11,7 @@ * [Delete a Subscription by ID](#delete-subscription) * [Handle notifications](#process-messages) * [Update secret for a Subscription](#update-subscription) +* [Version info endpoint](#version-info-endpoint) * [Current Limitations](#limitation) ## Introduction <a name="introduction"></a> @@ -600,6 +601,42 @@ curl --request PUT \ [Back to Table of Contents](#TOC) +## Version info endpoint +For deployment available public `/info` endpoint, which provides build and git related information. +#### Example response: +```json +{ + "groupId": "org.opengroup.osdu", + "artifactId": "storage-gcp", + "version": "0.10.0-SNAPSHOT", + "buildTime": "2021-07-09T14:29:51.584Z", + "branch": "feature/GONRG-2681_Build_info", + "commitId": "7777", + "commitMessage": "Added copyright to version info properties file", + "connectedOuterServices": [ + { + "name": "elasticSearch", + "version":"..." + }, + { + "name": "postgresSql", + "version":"..." + }, + { + "name": "redis", + "version":"..." + } + ] +} +``` +This endpoint takes information from files, generated by `spring-boot-maven-plugin`, +`git-commit-id-plugin` plugins. Need to specify paths for generated files to matching +properties: +- `version.info.buildPropertiesPath` +- `version.info.gitPropertiesPath` + +[Back to table of contents](#TOC) + ## Current Limitations <a name="limitation"></a> - There is no filtering applied on messages (such as based on the kind etc.) at the moment in OSDU. All the messages will be pushed to consumers. - Updates to existing records are notified as `create` event with attribute `recordUpdated` set to true. diff --git a/notification-core/pom.xml b/notification-core/pom.xml index 8ddbd8de557b76bf687e7f0d7dfe4bf67d779d83..b6f2e702fdcdb8a755d6e3890e08f7288063acb4 100644 --- a/notification-core/pom.xml +++ b/notification-core/pom.xml @@ -19,7 +19,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-core</name> <description>Core module for the notification service</description> <packaging>jar</packaging> @@ -27,7 +27,7 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> @@ -36,6 +36,8 @@ <maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.source>${java.version}</maven.compiler.source> <springfox-version>2.7.0</springfox-version> + <netty.version>4.1.65.Final</netty.version> + <undertow.version>2.1.7.Final</undertow.version> </properties> <dependencies> @@ -131,11 +133,6 @@ </exclusion> </exclusions> </dependency> - <dependency> - <groupId>io.undertow</groupId> - <artifactId>undertow-core</artifactId> - <version>2.0.27.Final</version> - </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> @@ -215,6 +212,83 @@ <artifactId>spring-test</artifactId> <scope>test</scope> </dependency> + + <!-- overriding packages with known vulnerabilities --> + <!-- See: https://nvd.nist.gov/vuln/search/results?form_type=Advanced&results_type=overview&search_type=all&cpe_vendor=cpe%3A%2F%3Anetty&cpe_product=cpe%3A%2F%3Anetty%3Anetty&cpe_version=cpe%3A%2F%3Anetty%3Anetty%3A4.1.38--> + <!-- See: https://ossindex.sonatype.org/component/pkg:maven/com.google.oauth-client/google-oauth-client@1.30.1?utm_source=dependency-check&utm_medium=integration&utm_content=6.1.6--> + <!-- See: https://nvd.nist.gov/vuln/search/results?form_type=Advanced&results_type=overview&search_type=all&cpe_vendor=cpe%3A%2F%3Aredhat&cpe_product=cpe%3A%2F%3Aredhat%3Aundertow&cpe_version=cpe%3A%2F%3Aredhat%3Aundertow%3A2.0.23--> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-transport</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-transport-native-unix-common</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-codec</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-buffer</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-common</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-resolver</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-handler</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-codec-http</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-codec-http2</artifactId> + <version>${netty.version}</version> + </dependency> + <dependency> + <groupId>io.netty</groupId> + <artifactId>netty-transport-native-epoll</artifactId> + <version>${netty.version}</version> + <classifier>linux-x86_64</classifier> + <type>jar</type> + </dependency> + <dependency> + <groupId>com.google.oauth-client</groupId> + <artifactId>google-oauth-client</artifactId> + <version>1.31.5</version> + </dependency> + <dependency> + <groupId>io.undertow</groupId> + <artifactId>undertow-core</artifactId> + <version>${undertow.version}</version> + </dependency> + <dependency> + <groupId>io.undertow</groupId> + <artifactId>undertow-servlet</artifactId> + <version>${undertow.version}</version> + </dependency> + <dependency> + <groupId>io.undertow</groupId> + <artifactId>undertow-websockets-jsr</artifactId> + <version>${undertow.version}</version> + </dependency> </dependencies> <build> <plugins> diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/api/InfoApi.java b/notification-core/src/main/java/org/opengroup/osdu/notification/api/InfoApi.java new file mode 100644 index 0000000000000000000000000000000000000000..2df52563f2c31b62d4056eb8884eb78d42df0b7d --- /dev/null +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/api/InfoApi.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.api; + +import java.io.IOException; +import org.opengroup.osdu.core.common.info.VersionInfoBuilder; +import org.opengroup.osdu.core.common.model.info.VersionInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping +public class InfoApi { + + @Autowired + private VersionInfoBuilder versionInfoBuilder; + + @GetMapping(value = "/info", produces = MediaType.APPLICATION_JSON_VALUE) + public VersionInfo info() throws IOException { + return versionInfoBuilder.buildVersionInfo(); + } +} \ No newline at end of file diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/api/PubsubEndpoint.java b/notification-core/src/main/java/org/opengroup/osdu/notification/api/PubsubEndpoint.java index d4407ed4382e596d3d6cf1e2d240ba8ab7857cef..33ea6281af557e5f028bd599e05f83779ccfe805 100644 --- a/notification-core/src/main/java/org/opengroup/osdu/notification/api/PubsubEndpoint.java +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/api/PubsubEndpoint.java @@ -16,29 +16,13 @@ package org.opengroup.osdu.notification.api; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Strings; -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import org.apache.http.HttpStatus; -import org.opengroup.osdu.core.common.cryptographic.ISignatureService; -import org.opengroup.osdu.core.common.http.HttpClient; -import org.opengroup.osdu.core.common.http.HttpRequest; import org.opengroup.osdu.core.common.http.HttpResponse; import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.notification.*; -import org.opengroup.osdu.core.common.notification.ISubscriptionFactory; -import org.opengroup.osdu.core.common.notification.ISubscriptionService; -import org.opengroup.osdu.core.common.notification.SubscriptionException; -import org.opengroup.osdu.notification.di.SubscriptionCacheFactory; -import org.opengroup.osdu.notification.provider.interfaces.IPubsubHandshakeHandler; import org.opengroup.osdu.notification.provider.interfaces.IPubsubRequestBodyExtractor; +import org.opengroup.osdu.notification.service.NotificationHandler; import org.opengroup.osdu.notification.utils.Config; -import org.opengroup.osdu.notification.provider.interfaces.IGoogleServiceAccount; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PostMapping; @@ -46,12 +30,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.annotation.RequestScope; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; import java.util.Map; - @RestController @RequestScope @RequestMapping("/push-handlers") @@ -59,29 +39,12 @@ public class PubsubEndpoint { @Autowired private IPubsubRequestBodyExtractor pubsubRequestBodyExtractor; @Autowired - private IPubsubHandshakeHandler pubsubHandshakeHandler; - @Autowired - private ISignatureService signatureService; - @Autowired - private HttpClient httpClient; - @Autowired - private IGoogleServiceAccount gsaTokenProvider; + private NotificationHandler notificationHandler; @Autowired private JaxRsDpsLog log; - @Autowired - private SubscriptionCacheFactory subscriptionCacheFactory; - @Autowired - private ISubscriptionFactory subscriptionFactory; - @Autowired - private DpsHeaders headers; - private static final String HMAC_TYPE = "HMAC"; - private static final String GSA_TYPE = "GSA"; - private final int WAITING_TIME = 30000; private final String ACKNOWLEDGE = "message acknowledged by client"; private final String NOT_ACKNOWLEDGE = "message not acknowledged by client"; - private static final Gson gson = new Gson(); - private ObjectMapper objectMapper; @PostMapping("/records-changed") @PreAuthorize("@authorizationFilter.hasAnyPermission('" + Config.OPS + "', '" + Config.PUBSUB + "')") @@ -89,92 +52,12 @@ public class PubsubEndpoint { String notificationId = this.pubsubRequestBodyExtractor.extractNotificationIdFromRequestBody(); String pubsubMessage = this.pubsubRequestBodyExtractor.extractDataFromRequestBody(); Map<String, String> headerAttributes = this.pubsubRequestBodyExtractor.extractAttributesFromRequestBody(); - - Subscription subscription = getSubscriptionFromCache(notificationId); - Secret secret = subscription.getSecret(); - String endpoint = subscription.getPushEndpoint(); - - String secretType = secret.getSecretType(); - String pushUrl = ""; - Map<String, String> requestHeader = new HashMap<>(); - - if (secretType.equalsIgnoreCase(HMAC_TYPE)) { - this.log.info("receiving pubsub message, will send out hmac type request, pubsub message: " + pubsubMessage); - HmacSecret hmacSecret = (HmacSecret) secret; - String signedjwt = this.signatureService.getSignedSignature(endpoint, hmacSecret.getValue()); - pushUrl = endpoint + "?hmac=" + signedjwt; - } else if (secretType.equalsIgnoreCase(GSA_TYPE)) { - this.log.info("receiving pubsub message, will send out gsa type request, pubsub message: " + pubsubMessage); - GsaSecret gsaSecret = (GsaSecret) secret; - GsaSecretValue gsaSecretValue = gsaSecret.getValue(); - - JsonParser jsonParser = new JsonParser(); - JsonElement root = jsonParser.parse(gsaSecretValue.getKey()); - String keyString = root.getAsJsonObject().toString(); - - String idToken = this.gsaTokenProvider.getIdToken(keyString, gsaSecretValue.getAudience()); - pushUrl = endpoint; - requestHeader.put("Authorization", idToken); - } - - this.log.info("sending out notification to endpoint: " + endpoint); - requestHeader.put(DpsHeaders.CONTENT_TYPE, "application/json"); - requestHeader.put(DpsHeaders.CORRELATION_ID, headerAttributes.get(DpsHeaders.CORRELATION_ID)); - requestHeader.put(DpsHeaders.DATA_PARTITION_ID, headerAttributes.get(DpsHeaders.DATA_PARTITION_ID)); - - HttpRequest request = HttpRequest.post().url(pushUrl).headers(requestHeader).body(pubsubMessage).connectionTimeout(WAITING_TIME).build(); - HttpResponse response = httpClient.send(request); + HttpResponse response = notificationHandler.notifySubscriber(notificationId, pubsubMessage, headerAttributes); if (!response.isSuccessCode()) { - this.log.error(NOT_ACKNOWLEDGE); - return ResponseEntity.badRequest().body(NOT_ACKNOWLEDGE); + this.log.error(NOT_ACKNOWLEDGE + response.getBody()); + return new ResponseEntity<String>(NOT_ACKNOWLEDGE, HttpStatus.valueOf(response.getResponseCode())); } - this.log.info(ACKNOWLEDGE); - return ResponseEntity.ok(ACKNOWLEDGE); - } - - private Subscription getSubscriptionFromCache(String notificationId) throws Exception { - String subscriptionString = subscriptionCacheFactory.get(notificationId); - try { - if (Strings.isNullOrEmpty(subscriptionString)) - subscriptionString = querySubscriptionAndUpdateCache(notificationId); - ObjectMapper objectMapper = this.getObjectMapper(); - Subscription subscription = objectMapper.readValue(subscriptionString, Subscription.class); - return subscription; - } catch (IOException e) { - this.log.warning("Error Parsing subscription String to object."); - throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Error in getting subscription for notificationId:" + notificationId, "Unexpected error in pushing message", e); - } catch (SubscriptionException se) { - this.log.warning("Error query subscription from registration."); - throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Error in getting subscription for notificationId:" + notificationId, "Unexpected error in pushing message", se); - } - } - - private String querySubscriptionAndUpdateCache(String notificationId) throws Exception { - ISubscriptionService service = subscriptionFactory.create(headers); - - List<Subscription> subscriptionList = service.query(notificationId); - if (subscriptionList == null || subscriptionList.size() == 0) { - this.log.warning(String.format("Subscription with notification ID %s not found in registration", notificationId)); - throw new AppException(HttpStatus.SC_NOT_FOUND, "Not found subscription for notificationId:" + notificationId, "Subscription not found"); - } - - Subscription subscription = subscriptionList.get(0); - String jsonSubscription = gson.toJson(subscription); - this.subscriptionCacheFactory.put(subscription.getNotificationId(), jsonSubscription); - - return jsonSubscription; - } - - //unit test purpose - protected ObjectMapper getObjectMapper() { - if (this.objectMapper == null) { - this.objectMapper = new ObjectMapper(); - } - return this.objectMapper; - } - - //unit test purpose - void setObjectMapper(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; + this.log.debug(ACKNOWLEDGE); + return new ResponseEntity<String>(ACKNOWLEDGE, HttpStatus.OK); } } diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/auth/AuthorizationFilter.java b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/AuthorizationFilter.java index 2eb23d060a1e3ce9e4abddd8fda4d2ce7a30e232..0d469306832359795a90ab339dd622f2a93a6d85 100644 --- a/notification-core/src/main/java/org/opengroup/osdu/notification/auth/AuthorizationFilter.java +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/AuthorizationFilter.java @@ -55,10 +55,9 @@ public class AuthorizationFilter { } String path = request.getServletPath(); - if ("GET".equals(request.getMethod())) { - if (path.equals("/swagger-ui.html")) { - return true; - } + if ("GET".equals(request.getMethod()) && + (path.equals("/swagger-ui.html") || path.equals("/info"))) { + return true; } if (Arrays.asList(requiredRoles).contains(Config.CRON) && requestInfoExt.isCronRequest()) { diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/auth/GsaAuth.java b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/GsaAuth.java new file mode 100644 index 0000000000000000000000000000000000000000..565c6635802339ffafe8499da66974e8ad164c0e --- /dev/null +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/GsaAuth.java @@ -0,0 +1,63 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.auth; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import org.opengroup.osdu.core.common.model.notification.GsaSecret; +import org.opengroup.osdu.core.common.model.notification.GsaSecretValue; +import org.opengroup.osdu.core.common.model.notification.Secret; +import org.opengroup.osdu.notification.auth.interfaces.SecretAuth; +import org.opengroup.osdu.notification.provider.interfaces.IGoogleServiceAccount; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class GsaAuth implements SecretAuth { + @Autowired + private IGoogleServiceAccount gsaTokenProvider; + + private GsaSecret gsaSecret; + + public void setSecret(Secret secret) { + this.gsaSecret = (GsaSecret) secret; + } + + public Secret getSecret() { + return this.gsaSecret; + } + + public String getPushUrl(String endpoint) throws Exception { + return endpoint; + } + + public Map<String, String> getRequestHeaders() { + Map<String, String> requestHeader = new HashMap<>(); + if (gsaSecret != null) { + GsaSecretValue gsaSecretValue = gsaSecret.getValue(); + JsonParser jsonParser = new JsonParser(); + JsonElement root = jsonParser.parse(gsaSecretValue.getKey()); + String keyString = root.getAsJsonObject().toString(); + String idToken = this.gsaTokenProvider.getIdToken(keyString, gsaSecretValue.getAudience()); + requestHeader.put("Authorization", idToken); + } + return requestHeader; + } +} diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/auth/HmacAuth.java b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/HmacAuth.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2885e2d2aeb91b62e65564a0d37a10a282bb73 --- /dev/null +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/HmacAuth.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.auth; + +import org.opengroup.osdu.core.common.cryptographic.ISignatureService; +import org.opengroup.osdu.core.common.model.notification.HmacSecret; +import org.opengroup.osdu.core.common.model.notification.Secret; +import org.opengroup.osdu.notification.auth.interfaces.SecretAuth; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class HmacAuth implements SecretAuth { + @Autowired + private ISignatureService signatureService; + + private HmacSecret hmacSecret; + + public void setSecret(Secret secret) { + this.hmacSecret = (HmacSecret) secret; + } + + public Secret getSecret() { + return this.hmacSecret; + } + + public String getPushUrl(String endpoint) throws Exception { + String pushUrl = endpoint; + String signedjwt = this.signatureService.getSignedSignature(endpoint, hmacSecret.getValue()); + pushUrl += "?hmac=" + signedjwt; + return pushUrl; + } + + public Map<String, String> getRequestHeaders() { + Map<String, String> requestHeader = new HashMap<>(); + return requestHeader; + + } +} diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/auth/factory/AuthFactory.java b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/factory/AuthFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..5cedb532daabcf4806eb30dfffbbd97878b38750 --- /dev/null +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/factory/AuthFactory.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.auth.factory; + +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.notification.auth.GsaAuth; +import org.opengroup.osdu.notification.auth.HmacAuth; +import org.opengroup.osdu.notification.auth.interfaces.SecretAuth; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +public class AuthFactory { + private final String HMAC_TYPE = "HMAC"; + private final String GSA_TYPE = "GSA"; + + @Autowired + private HmacAuth hmacAuth; + @Autowired + private GsaAuth gsaAuth; + @Autowired + private JaxRsDpsLog log; + + public SecretAuth getSecretAuth(String secretType) { + switch (secretType.toUpperCase()) { + case HMAC_TYPE: + return hmacAuth; + case GSA_TYPE: + return gsaAuth; + default: + throw new AppException(404, "Secret Type Not Found", "Unrecognised secret type encountered :" + secretType); + } + } +} diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/auth/interfaces/SecretAuth.java b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/interfaces/SecretAuth.java new file mode 100644 index 0000000000000000000000000000000000000000..487a7f0acac68bcf12ddbfe0b05a6df742cb2d51 --- /dev/null +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/auth/interfaces/SecretAuth.java @@ -0,0 +1,30 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.auth.interfaces; + +import org.opengroup.osdu.core.common.model.notification.Secret; + +import java.util.Map; + +public interface SecretAuth { + String getPushUrl(String endpoint) throws Exception; + + void setSecret(Secret secret); + + Secret getSecret(); + + Map<String, String> getRequestHeaders(); +} diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/di/CredentialHeadersProvider.java b/notification-core/src/main/java/org/opengroup/osdu/notification/di/CredentialHeadersProvider.java index c11ea04b671ed028c02c54fbe2edac0df2d894c5..85dc9d1dcc0d7582343942d33e862ab4a1a1c71a 100644 --- a/notification-core/src/main/java/org/opengroup/osdu/notification/di/CredentialHeadersProvider.java +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/di/CredentialHeadersProvider.java @@ -21,6 +21,7 @@ import org.opengroup.osdu.core.common.util.IServiceAccountJwtClient; import org.opengroup.osdu.notification.provider.interfaces.IPubsubRequestBodyExtractor; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -33,8 +34,9 @@ import org.springframework.web.context.annotation.RequestScope; import javax.servlet.http.HttpServletRequest; @Component -@Primary @RequestScope +@ConditionalOnProperty(value = "requestScope.enabled", havingValue = "true", matchIfMissing = true) +@Primary public class CredentialHeadersProvider implements FactoryBean<DpsHeaders> { @Autowired diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/di/SubscriptionCacheFactory.java b/notification-core/src/main/java/org/opengroup/osdu/notification/di/SubscriptionCacheFactory.java index 5879641c8219a4dd4af318e5ce8da70ca3d5b0c5..0291e67f48be2d75a689fce3caa0d010ebd116ed 100644 --- a/notification-core/src/main/java/org/opengroup/osdu/notification/di/SubscriptionCacheFactory.java +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/di/SubscriptionCacheFactory.java @@ -19,7 +19,10 @@ package org.opengroup.osdu.notification.di; import org.opengroup.osdu.core.common.cache.ICache; import org.opengroup.osdu.core.common.cache.MultiTenantCache; import org.opengroup.osdu.core.common.cache.VmCache; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; import org.opengroup.osdu.core.common.model.tenant.TenantInfo; +import org.opengroup.osdu.core.common.provider.interfaces.ITenantFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -27,7 +30,9 @@ import org.springframework.stereotype.Component; @Component public class SubscriptionCacheFactory implements ICache<String, String> { @Autowired - private TenantInfo tenant; + private DpsHeaders headers; + @Autowired + private ITenantFactory tenantFactory; private MultiTenantCache<String> caches; @@ -56,6 +61,10 @@ public class SubscriptionCacheFactory implements ICache<String, String> { } private ICache<String, String> partitionCache() { - return this.caches.get(String.format("%s:subscription", this.tenant.getDataPartitionId())); + TenantInfo tenantInfo = this.tenantFactory.getTenantInfo(this.headers.getPartitionIdWithFallbackToAccountId()); + if (tenantInfo == null) { + throw AppException.createUnauthorized(String.format("could not retrieve tenant info for data partition id: %s", this.headers.getPartitionIdWithFallbackToAccountId())); + } + return this.caches.get(String.format("%s:subscription", tenantInfo.getDataPartitionId())); } } diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/service/NotificationHandler.java b/notification-core/src/main/java/org/opengroup/osdu/notification/service/NotificationHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..9ba4a823ab5d54fa97e5f79ffa8bc2a926ed09cb --- /dev/null +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/service/NotificationHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.service; + +import org.opengroup.osdu.core.common.http.HttpClient; +import org.opengroup.osdu.core.common.http.HttpRequest; +import org.opengroup.osdu.core.common.http.HttpResponse; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.notification.*; +import org.opengroup.osdu.notification.auth.factory.AuthFactory; +import org.opengroup.osdu.notification.auth.interfaces.SecretAuth; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class NotificationHandler { + private final static Logger LOGGER = LoggerFactory.getLogger(NotificationHandler.class); + @Autowired + private HttpClient httpClient; + @Autowired + private SubscriptionHandler subscriptionHandler; + @Autowired + private AuthFactory authFactory; + @Value("${app.waitingTime:30000}") + private int WAITING_TIME; + + public HttpResponse notifySubscriber(String notificationId, String pubsubMessage, Map<String, String> headerAttributes) throws Exception { + Subscription subscription = subscriptionHandler.getSubscriptionFromCache(notificationId); + Secret secret = subscription.getSecret(); + String endpoint = subscription.getPushEndpoint(); + String secretType = secret.getSecretType(); + String pushUrl = ""; + Map<String, String> requestHeader = new HashMap<String, String>(); + + // Authentication Secret + SecretAuth secretAuth = authFactory.getSecretAuth(secretType); + secretAuth.setSecret(secret); + pushUrl = secretAuth.getPushUrl(endpoint); + requestHeader = secretAuth.getRequestHeaders(); + + requestHeader.put(DpsHeaders.CONTENT_TYPE, "application/json"); + requestHeader.put(DpsHeaders.CORRELATION_ID, headerAttributes.get(DpsHeaders.CORRELATION_ID)); + requestHeader.put(DpsHeaders.DATA_PARTITION_ID, headerAttributes.get(DpsHeaders.DATA_PARTITION_ID)); + + HttpRequest request = HttpRequest.post().url(pushUrl).headers(requestHeader).body(pubsubMessage).connectionTimeout(WAITING_TIME).build(); + HttpResponse response = httpClient.send(request); + this.LOGGER.debug("Sending out notification to endpoint: " + endpoint); + return response; + } +} diff --git a/notification-core/src/main/java/org/opengroup/osdu/notification/service/SubscriptionHandler.java b/notification-core/src/main/java/org/opengroup/osdu/notification/service/SubscriptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..016ca4f49a55e630b95bbdf0c7ace92359c25a02 --- /dev/null +++ b/notification-core/src/main/java/org/opengroup/osdu/notification/service/SubscriptionHandler.java @@ -0,0 +1,93 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Strings; +import com.google.gson.Gson; +import org.apache.http.HttpStatus; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.notification.Subscription; +import org.opengroup.osdu.core.common.notification.ISubscriptionFactory; +import org.opengroup.osdu.core.common.notification.ISubscriptionService; +import org.opengroup.osdu.core.common.notification.SubscriptionException; +import org.opengroup.osdu.notification.di.SubscriptionCacheFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.List; + +@Component +public class SubscriptionHandler { + @Autowired + private ISubscriptionFactory subscriptionFactory; + @Autowired + private SubscriptionCacheFactory subscriptionCacheFactory; + @Autowired + private JaxRsDpsLog log; + @Autowired + private DpsHeaders headers; + + private static final Gson gson = new Gson(); + private ObjectMapper objectMapper; + + public Subscription getSubscriptionFromCache(String notificationId) throws IOException, SubscriptionException { + String subscriptionString = subscriptionCacheFactory.get(notificationId); + try { + if (Strings.isNullOrEmpty(subscriptionString)) + subscriptionString = querySubscriptionAndUpdateCache(notificationId); + ObjectMapper objectMapper = this.getObjectMapper(); + Subscription subscription = objectMapper.readValue(subscriptionString, Subscription.class); + return subscription; + } catch (IOException e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Error Parsing subscription String to object", "Unexpected error in pushing message", e); + } catch (SubscriptionException se) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Error query subscription from registration", "Unexpected error in pushing message", se); + } + } + + private String querySubscriptionAndUpdateCache(String notificationId) throws AppException, SubscriptionException { + ISubscriptionService service = subscriptionFactory.create(headers); + + List<Subscription> subscriptionList = service.query(notificationId); + if (subscriptionList == null || subscriptionList.size() == 0) { + throw new AppException(HttpStatus.SC_NOT_FOUND, "Not found subscription for notificationId:" + notificationId, "Subscription not found"); + } + + Subscription subscription = subscriptionList.get(0); + String jsonSubscription = gson.toJson(subscription); + this.subscriptionCacheFactory.put(subscription.getNotificationId(), jsonSubscription); + + return jsonSubscription; + } + + //unit test purpose + protected ObjectMapper getObjectMapper() { + if (this.objectMapper == null) { + this.objectMapper = new ObjectMapper(); + } + return this.objectMapper; + } + + //unit test purpose + void setObjectMapper(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } +} diff --git a/notification-core/src/test/java/org/opengroup/osdu/notification/api/InfoApiTest.java b/notification-core/src/test/java/org/opengroup/osdu/notification/api/InfoApiTest.java new file mode 100644 index 0000000000000000000000000000000000000000..343678e12a30ec13bcc98959d3166b55a10212a4 --- /dev/null +++ b/notification-core/src/test/java/org/opengroup/osdu/notification/api/InfoApiTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.api; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.opengroup.osdu.core.common.info.VersionInfoBuilder; +import org.opengroup.osdu.core.common.model.info.VersionInfo; + +@RunWith(MockitoJUnitRunner.class) +public class InfoApiTest { + + @InjectMocks + private InfoApi sut; + + @Mock + private VersionInfoBuilder versionInfoBuilder; + + @Test + public void should_return200_getVersionInfo() throws IOException { + VersionInfo versionInfo = VersionInfo.builder() + .groupId("group") + .artifactId("artifact") + .version("0.1.0") + .buildTime("1000") + .branch("master") + .commitId("7777") + .commitMessage("Merge commit") + .build(); + when(versionInfoBuilder.buildVersionInfo()).thenReturn(versionInfo); + VersionInfo response = this.sut.info(); + assertEquals(versionInfo, response); + } +} diff --git a/notification-core/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointTests.java b/notification-core/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointTests.java index 707a59fc951ab821160f71d35f766bc68ff39eff..ce98167953803ac6b257a31f38e5efb621fe27f0 100644 --- a/notification-core/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointTests.java +++ b/notification-core/src/test/java/org/opengroup/osdu/notification/api/PubsubEndpointTests.java @@ -16,39 +16,26 @@ package org.opengroup.osdu.notification.api; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.opengroup.osdu.core.common.cryptographic.ISignatureService; -import org.opengroup.osdu.core.common.http.HttpClient; import org.opengroup.osdu.core.common.http.HttpResponse; import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.core.common.model.http.AppException; import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.core.common.model.notification.Subscription; -import org.opengroup.osdu.core.common.notification.ISubscriptionService; import org.opengroup.osdu.core.common.notification.SubscriptionException; -import org.opengroup.osdu.core.common.notification.SubscriptionFactory; -import org.opengroup.osdu.core.common.notification.SubscriptionService; -import org.opengroup.osdu.notification.di.CredentialHeadersProvider; -import org.opengroup.osdu.notification.di.SubscriptionCacheFactory; -import org.opengroup.osdu.notification.provider.interfaces.IGoogleServiceAccount; import org.opengroup.osdu.notification.provider.interfaces.IPubsubRequestBodyExtractor; +import org.opengroup.osdu.notification.service.NotificationHandler; import org.powermock.modules.junit4.PowerMockRunner; import org.springframework.http.ResponseEntity; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @RunWith(PowerMockRunner.class) @@ -56,31 +43,14 @@ public class PubsubEndpointTests { @Mock private IPubsubRequestBodyExtractor pubsubRequestBodyExtractor; @Mock - private ISignatureService signatureService; - @Mock - private HttpClient httpClient; - @Mock - private DpsHeaders headers; - @Mock - private SubscriptionCacheFactory subscriptionCacheFactory; - @Mock - private IGoogleServiceAccount googleIdTokenProducer; + private NotificationHandler notificationHandler; @Mock private JaxRsDpsLog log; @InjectMocks private PubsubEndpoint sut; - @Mock - private CredentialHeadersProvider credentialHeadersProvider; - @Mock - private SubscriptionFactory subscriptionFactory; - @Mock - private ObjectMapper objectMapper; - private static final String SIGNED_SIGNATURE = "testEncodedInfo.testSignature"; - private static final String GOOGLE_ID_TOKEN = "testHeader.testPayload.testSignature"; private static final String NOTIFICATION_ID = "test-notification-id"; private static final String PUBSUB_MESSAGE = "test-pubsub-message-data"; - private HttpResponse response = new HttpResponse(); @Before @@ -90,163 +60,28 @@ public class PubsubEndpointTests { } @Test - public void should_return200_whenPubsubMessageValidAndSuccessCodeReturnedFromClient_hmac() throws Exception { - response.setResponseCode(200); - - when(this.signatureService.getSignedSignature(any(), any())).thenReturn(SIGNED_SIGNATURE); - when(this.httpClient.send(any())).thenReturn(response); - when(this.subscriptionCacheFactory.get(any())).thenReturn(getHmacSubscription()); - sut.setObjectMapper(new ObjectMapper()); - ResponseEntity responseEntity = this.sut.recordChanged(); - Assert.assertEquals(200, responseEntity.getStatusCode().value()); - Assert.assertEquals("message acknowledged by client", responseEntity.getBody().toString()); - } - - @Test - public void should_return200_whenPubsubMessageValidAndSuccessCodeReturnedFromClient_gsa() throws Exception { + public void should_return200_whenPubsubMessageValidAndSuccessCodeReturnedFromNotificationHandler() throws Exception { response.setResponseCode(200); - - when(this.googleIdTokenProducer.getIdToken(any(), any())).thenReturn(GOOGLE_ID_TOKEN); - when(this.httpClient.send(any())).thenReturn(response); - when(this.subscriptionCacheFactory.get(any())).thenReturn(getGsaSubscription()); - sut.setObjectMapper(new ObjectMapper()); - + when(this.notificationHandler.notifySubscriber(NOTIFICATION_ID, PUBSUB_MESSAGE, new HashMap<>())).thenReturn(response); ResponseEntity responseEntity = this.sut.recordChanged(); Assert.assertEquals(200, responseEntity.getStatusCode().value()); Assert.assertEquals("message acknowledged by client", responseEntity.getBody().toString()); } @Test - public void should_return400_whenSendOutRequestButNoSuccessCodeReturned_hmac() throws Exception { - response.setResponseCode(500); - - when(this.signatureService.getSignedSignature(any(), any())).thenReturn(SIGNED_SIGNATURE); - when(this.httpClient.send(any())).thenReturn(response); - when(this.subscriptionCacheFactory.get(any())).thenReturn(getHmacSubscription()); - sut.setObjectMapper(new ObjectMapper()); - - ResponseEntity responseEntity = this.sut.recordChanged(); - Assert.assertEquals(400, responseEntity.getStatusCode().value()); - Assert.assertEquals("message not acknowledged by client", responseEntity.getBody().toString()); - } - - @Test - public void should_return400_whenSendOutRequestButNoSuccessCodeReturned_gsa() throws Exception { - response.setResponseCode(500); - - when(this.googleIdTokenProducer.getIdToken(any(), any())).thenReturn(GOOGLE_ID_TOKEN); - when(this.httpClient.send(any())).thenReturn(response); - when(this.subscriptionCacheFactory.get(any())).thenReturn(getGsaSubscription()); - sut.setObjectMapper(new ObjectMapper()); - + public void should_return400_whenPubsubMessageValidAndFailureCodeReturnedFromNotificationHandler() throws Exception { + response.setResponseCode(400); + when(this.notificationHandler.notifySubscriber(NOTIFICATION_ID, PUBSUB_MESSAGE, new HashMap<>())).thenReturn(response); ResponseEntity responseEntity = this.sut.recordChanged(); Assert.assertEquals(400, responseEntity.getStatusCode().value()); Assert.assertEquals("message not acknowledged by client", responseEntity.getBody().toString()); } - @Test(expected = AppException.class) - public void should_throwException_whenSubscriptionNotFound() throws Exception { - when(this.subscriptionCacheFactory.get(any())).thenReturn(null); - when(this.credentialHeadersProvider.getObject()).thenReturn(new DpsHeaders()); - ISubscriptionService subscriptionService = mock(SubscriptionService.class); - when(this.subscriptionFactory.create(any())).thenReturn(subscriptionService); - - HttpResponse response = new HttpResponse(); - when(subscriptionService.query(any())).thenThrow(new SubscriptionException("error", response)); - this.sut.recordChanged(); - fail("should throw AppException"); - } - - @Test(expected = AppException.class) - public void should_throwException_whenSubscriptionParsingErrorOccurs() throws Exception { - - when(this.subscriptionCacheFactory.get(any())).thenReturn(null); - when(this.credentialHeadersProvider.getObject()).thenReturn(new DpsHeaders()); - ISubscriptionService subscriptionService = mock(SubscriptionService.class); - when(this.subscriptionFactory.create(any())).thenReturn(subscriptionService); - - String jsonSubscription = this.getHmacSubscription(); - ObjectMapper objectMapper = new ObjectMapper(); - Subscription subscription = objectMapper.readValue(jsonSubscription, Subscription.class); - List<Subscription> queryResult = new ArrayList<>(); - queryResult.add(subscription); - - when(subscriptionService.query(any())).thenReturn(queryResult); - - sut.setObjectMapper(this.objectMapper); - when(this.objectMapper.readValue(anyString(), any(Class.class))).thenThrow(new IOException()); + @Test(expected = Exception.class) + public void should_return400_whenPubsubMessageValidAndNotificationHandlerThrowsException() throws Exception { + response.setResponseCode(400); + when(this.notificationHandler.notifySubscriber(NOTIFICATION_ID, PUBSUB_MESSAGE, new HashMap<>())).thenThrow(new Exception("error")); this.sut.recordChanged(); - fail("should throw AppException"); - } - - @Test - public void should_return200_whenSubscriptionGotFromRegistration() throws Exception { - response.setResponseCode(200); - - when(this.googleIdTokenProducer.getIdToken(any(), any())).thenReturn(GOOGLE_ID_TOKEN); - when(this.httpClient.send(any())).thenReturn(response); - - when(this.subscriptionCacheFactory.get(any())).thenReturn(null); - when(this.credentialHeadersProvider.getObject()).thenReturn(new DpsHeaders()); - ISubscriptionService subscriptionService = mock(SubscriptionService.class); - when(this.subscriptionFactory.create(any())).thenReturn(subscriptionService); - sut.setObjectMapper(new ObjectMapper()); - - String jsonSubscription = this.getHmacSubscription(); - ObjectMapper objectMapper = new ObjectMapper(); - // unit test purpose, not sure if this is osdu compliant - Subscription subscription = objectMapper.readValue(jsonSubscription, Subscription.class); - List<Subscription> queryResult = new ArrayList<>(); - queryResult.add(subscription); - when(subscriptionService.query(any())).thenReturn(queryResult); - ResponseEntity responseEntity = this.sut.recordChanged(); - Assert.assertEquals(200, responseEntity.getStatusCode().value()); - Assert.assertEquals("message acknowledged by client", responseEntity.getBody().toString()); - } - - @Test(expected = IllegalArgumentException.class) - public void should_throwException_whenErrorGeneratingSignature_hmac() throws Exception { - IllegalArgumentException ex = new IllegalArgumentException("Error generating signed signature"); - - when(this.subscriptionCacheFactory.get(any())).thenReturn(getHmacSubscription()); - when(this.signatureService.getSignedSignature(any(), any())).thenThrow(ex); - sut.setObjectMapper(new ObjectMapper()); - - this.sut.recordChanged(); - } - - private String getHmacSubscription() { - return "{\n" + - "\t\"name\": \"testSubscription\",\n" + - "\t\"description\": \"Description\",\n" + - "\t\"topic\": \"records-changed\",\n" + - "\t\"pushEndpoint\": \"http://challenge\",\n" + - "\t\"notificationId\": \"notificationId\",\n" + - "\t\"id\": \"id_1\",\n" + - "\t\"createdBy\": \"test@test.com\",\n" + - "\t\"secret\": {\n" + - "\t\t\"secretType\": \"HMAC\",\n" + - "\t\t\"value\": \"testsecret\"\n" + - "\t}\n" + - "}"; - } - - private String getGsaSubscription() { - return "{\n" + - "\t\"name\": \"testSubscription\",\n" + - "\t\"description\": \"Description\",\n" + - "\t\"topic\": \"records-changed\",\n" + - "\t\"pushEndpoint\": \"http:///gsa-challenge\",\n" + - "\t\"notificationId\": \"notificationId\",\n" + - "\t\"id\": \"id_1\",\n" + - "\t\"createdBy\": \"test@test.com\",\n" + - "\t\"secret\": {\n" + - "\t\t\"secretType\": \"GSA\",\n" + - "\t\t\"value\": {\n" + - "\t\t\t\"audience\":\"audience\",\n" + - "\t\t\t\"key\":\"keyFile\"\n" + - "\t\t}\n" + - "\t}\n" + - "}"; + fail("should throw Exception"); } } diff --git a/notification-core/src/test/java/org/opengroup/osdu/notification/auth/GsaAuthTest.java b/notification-core/src/test/java/org/opengroup/osdu/notification/auth/GsaAuthTest.java new file mode 100644 index 0000000000000000000000000000000000000000..077db3917a777340d9483ac7fe0621ba770a9fed --- /dev/null +++ b/notification-core/src/test/java/org/opengroup/osdu/notification/auth/GsaAuthTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.auth; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.model.notification.GsaSecret; +import org.opengroup.osdu.core.common.model.notification.GsaSecretValue; +import org.opengroup.osdu.core.common.model.notification.Subscription; +import org.opengroup.osdu.notification.provider.interfaces.IGoogleServiceAccount; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.utils.Asserts; + +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +public class GsaAuthTest { + @Mock + private IGoogleServiceAccount gsaTokenProvider; + @InjectMocks + private GsaAuth sut; + + private static Subscription gsa_subscription; + private static final String GOOGLE_ID_TOKEN = "testHeader.testPayload.testSignature"; + + @BeforeClass + public static void setup() { + setGsa_subscription(); + } + + @Test + public void should_return_valid_EndpointAndHeaders_gsaClient() throws Exception { + GsaSecret secret = (GsaSecret) gsa_subscription.getSecret(); + when(this.gsaTokenProvider.getIdToken(any(), any())).thenReturn(GOOGLE_ID_TOKEN); + sut.setSecret(gsa_subscription.getSecret()); + Asserts.assertNotNull(sut.getSecret(), "Unable to set Secret"); + Map<String, String> headers = sut.getRequestHeaders(); + Assert.assertEquals(GOOGLE_ID_TOKEN, headers.get("Authorization")); + String pushUrl = sut.getPushUrl(gsa_subscription.getPushEndpoint()); + Assert.assertEquals(pushUrl, gsa_subscription.getPushEndpoint()); + } + + @Test + public void should_return_emptyHeaders_when_secret_is_null() throws Exception { + GsaSecret secret = (GsaSecret) gsa_subscription.getSecret(); + when(this.gsaTokenProvider.getIdToken(any(), any())).thenReturn(GOOGLE_ID_TOKEN); + Map<String, String> headers = sut.getRequestHeaders(); + Assert.assertEquals(0, headers.size()); + String pushUrl = sut.getPushUrl(gsa_subscription.getPushEndpoint()); + Assert.assertEquals(pushUrl, gsa_subscription.getPushEndpoint()); + } + + private static void setGsa_subscription() { + gsa_subscription = new Subscription(); + gsa_subscription.setName("gsa_test_subscription"); + gsa_subscription.setPushEndpoint("http:///gsa-challenge"); + gsa_subscription.setDescription("Description"); + gsa_subscription.setTopic("records-changed"); + gsa_subscription.setNotificationId("test-notification-id"); + gsa_subscription.setId("id_1"); + gsa_subscription.setCreatedBy("test@test.com"); + GsaSecret secret = new GsaSecret(); + GsaSecretValue value = new GsaSecretValue(); + value.setAudience("audience"); + value.setKey("{\"keyFile\":{\"key\":\"gsa\"}}"); + secret.setValue(value); + gsa_subscription.setSecret(secret); + } + +} diff --git a/notification-core/src/test/java/org/opengroup/osdu/notification/auth/HmacAuthTest.java b/notification-core/src/test/java/org/opengroup/osdu/notification/auth/HmacAuthTest.java new file mode 100644 index 0000000000000000000000000000000000000000..130beb4d81c0392cb9896527b25bfbc347533709 --- /dev/null +++ b/notification-core/src/test/java/org/opengroup/osdu/notification/auth/HmacAuthTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.auth; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.cryptographic.ISignatureService; +import org.opengroup.osdu.core.common.model.notification.HmacSecret; +import org.opengroup.osdu.core.common.model.notification.Subscription; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.Map; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +public class HmacAuthTest { + @Mock + private ISignatureService signatureService; + @InjectMocks + private HmacAuth sut; + + private static Subscription hmac_subscription; + private static final String SIGNED_SIGNATURE = "testEncodedInfo.testSignature"; + + @BeforeClass + public static void setup() { + setHmac_subscription(); + } + + @Test + public void should_return_valid_EndpointAndHeaders() throws Exception { + HmacSecret secret = (HmacSecret) hmac_subscription.getSecret(); + when(this.signatureService.getSignedSignature(hmac_subscription.getPushEndpoint(), secret.getValue())).thenReturn(SIGNED_SIGNATURE); + sut.setSecret(hmac_subscription.getSecret()); + Assert.assertEquals(secret, sut.getSecret()); + Map<String, String> headers = sut.getRequestHeaders(); + Assert.assertEquals(0, headers.size()); + String pushUrl = sut.getPushUrl(hmac_subscription.getPushEndpoint()); + Assert.assertEquals(hmac_subscription.getPushEndpoint() + "?hmac=" + SIGNED_SIGNATURE, pushUrl); + } + + @Test(expected = Exception.class) + public void should_throwException_whenErrorGeneratingSignature_hmac() throws Exception { + Exception ex = new Exception("Error generating signed signature"); + HmacSecret secret = (HmacSecret) hmac_subscription.getSecret(); + when(this.signatureService.getSignedSignature(hmac_subscription.getPushEndpoint(), secret.getValue())).thenThrow(ex); + sut.getPushUrl(hmac_subscription.getPushEndpoint()); + fail("should throw Exception"); + } + + private static void setHmac_subscription() { + hmac_subscription = new Subscription(); + hmac_subscription.setName("hamc_test_subscription"); + hmac_subscription.setPushEndpoint("http://challenge"); + hmac_subscription.setDescription("Description"); + hmac_subscription.setTopic("records-changed"); + hmac_subscription.setNotificationId("test-notification-id"); + hmac_subscription.setId("id_1"); + hmac_subscription.setCreatedBy("test@test.com"); + HmacSecret secret = new HmacSecret(); + secret.setValue("testsecret"); + hmac_subscription.setSecret(secret); + } +} diff --git a/notification-core/src/test/java/org/opengroup/osdu/notification/service/NotificationHandlerTests.java b/notification-core/src/test/java/org/opengroup/osdu/notification/service/NotificationHandlerTests.java new file mode 100644 index 0000000000000000000000000000000000000000..0c5f8636d7e9c9d2d3c4fc8ea652372524361415 --- /dev/null +++ b/notification-core/src/test/java/org/opengroup/osdu/notification/service/NotificationHandlerTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.service; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.http.HttpClient; +import org.opengroup.osdu.core.common.http.HttpResponse; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.notification.*; +import org.opengroup.osdu.core.common.notification.SubscriptionException; +import org.opengroup.osdu.notification.auth.factory.AuthFactory; +import org.opengroup.osdu.notification.auth.interfaces.SecretAuth; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +public class NotificationHandlerTests { + @Mock + private SubscriptionHandler subscriptionHandler; + @Mock + private AuthFactory authFactory; + @Mock + private SecretAuth secretAuth; + @Mock + private HttpClient httpClient; + @Mock + private JaxRsDpsLog log; + @InjectMocks + private NotificationHandler sut; + + private HttpResponse response = new HttpResponse(); + private static final String NOTIFICATION_ID = "test-notification-id"; + private static final String PUBSUB_MESSAGE = "test-pubsub-message-data"; + private static Subscription gsa_subscription; + private static Subscription hmac_subscription; + + @BeforeClass + public static void setup() { + setHmac_subscription(); + setGsa_subscription(); + } + + @Test + public void should_return200_whenPubsubMessageValidAndSuccessCodeReturnedFromClient_gsa() throws Exception { + response.setResponseCode(200); + Map<String, String> headers = new HashMap<String, String>(); + when(this.subscriptionHandler.getSubscriptionFromCache(this.NOTIFICATION_ID)).thenReturn(gsa_subscription); + when(this.authFactory.getSecretAuth(any())).thenReturn(secretAuth); + when(this.httpClient.send(any())).thenReturn(response); + when(this.secretAuth.getPushUrl(gsa_subscription.getPushEndpoint())).thenReturn(gsa_subscription.getPushEndpoint()); + when(this.secretAuth.getRequestHeaders()).thenReturn(headers); + HttpResponse response = this.sut.notifySubscriber(this.NOTIFICATION_ID, this.PUBSUB_MESSAGE, headers); + Assert.assertEquals(200, response.getResponseCode()); + } + + @Test + public void should_return200_whenPubsubMessageValidAndSuccessCodeReturnedFromClient_hmac() throws Exception { + response.setResponseCode(200); + Map<String, String> headers = new HashMap<String, String>(); + when(this.subscriptionHandler.getSubscriptionFromCache(this.NOTIFICATION_ID)).thenReturn(hmac_subscription); + when(this.authFactory.getSecretAuth(any())).thenReturn(secretAuth); + when(this.httpClient.send(any())).thenReturn(response); + when(this.secretAuth.getPushUrl(hmac_subscription.getPushEndpoint())).thenReturn(hmac_subscription.getPushEndpoint()); + when(this.secretAuth.getRequestHeaders()).thenReturn(headers); + HttpResponse response = this.sut.notifySubscriber(this.NOTIFICATION_ID, this.PUBSUB_MESSAGE, headers); + Assert.assertEquals(200, response.getResponseCode()); + } + + @Test(expected = SubscriptionException.class) + public void should_throwException_whenSubscriptionHandlerThrowsException() throws Exception { + Map<String, String> headers = new HashMap<String, String>(); + when(this.authFactory.getSecretAuth(any())).thenReturn(secretAuth); + when(this.httpClient.send(any())).thenReturn(response); + when(this.secretAuth.getPushUrl(gsa_subscription.getPushEndpoint())).thenReturn(gsa_subscription.getPushEndpoint()); + when(this.secretAuth.getRequestHeaders()).thenReturn(headers); + when(subscriptionHandler.getSubscriptionFromCache(this.NOTIFICATION_ID)).thenThrow(new SubscriptionException("error", response)); + this.sut.notifySubscriber(this.NOTIFICATION_ID, this.PUBSUB_MESSAGE, headers); + fail("should throw SubscriptionException"); + } + + private static void setGsa_subscription() { + gsa_subscription = new Subscription(); + gsa_subscription.setName("gsa_test_subscription"); + gsa_subscription.setPushEndpoint("http:///gsa-challenge"); + gsa_subscription.setDescription("Description"); + gsa_subscription.setTopic("records-changed"); + gsa_subscription.setNotificationId(NOTIFICATION_ID); + gsa_subscription.setId("id_1"); + gsa_subscription.setCreatedBy("test@test.com"); + GsaSecret secret = new GsaSecret(); + GsaSecretValue value = new GsaSecretValue(); + value.setAudience("audience"); + value.setKey("keyFile"); + secret.setValue(value); + gsa_subscription.setSecret(secret); + + } + + private static void setHmac_subscription() { + hmac_subscription = new Subscription(); + hmac_subscription.setName("hamc_test_subscription"); + hmac_subscription.setPushEndpoint("http://challenge"); + hmac_subscription.setDescription("Description"); + hmac_subscription.setTopic("records-changed"); + hmac_subscription.setNotificationId(NOTIFICATION_ID); + hmac_subscription.setId("id_1"); + hmac_subscription.setCreatedBy("test@test.com"); + HmacSecret secret = new HmacSecret(); + secret.setValue("testsecret"); + hmac_subscription.setSecret(secret); + } +} diff --git a/notification-core/src/test/java/org/opengroup/osdu/notification/service/SubscriptionHandlerTests.java b/notification-core/src/test/java/org/opengroup/osdu/notification/service/SubscriptionHandlerTests.java new file mode 100644 index 0000000000000000000000000000000000000000..aefb8b62c74d2a68000f07e2b713b085c0ebbc6a --- /dev/null +++ b/notification-core/src/test/java/org/opengroup/osdu/notification/service/SubscriptionHandlerTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2017-2020, 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.opengroup.osdu.notification.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.http.HttpResponse; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.notification.Subscription; +import org.opengroup.osdu.core.common.notification.ISubscriptionFactory; +import org.opengroup.osdu.core.common.notification.ISubscriptionService; +import org.opengroup.osdu.core.common.notification.SubscriptionException; +import org.opengroup.osdu.core.common.notification.SubscriptionService; +import org.opengroup.osdu.notification.di.SubscriptionCacheFactory; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +public class SubscriptionHandlerTests { + @Mock + private ISubscriptionFactory subscriptionFactory; + @Mock + private SubscriptionCacheFactory subscriptionCacheFactory; + @Mock + private ObjectMapper objectMapper; + @InjectMocks + private SubscriptionHandler sut; + + private static final String NOTIFICATION_ID = "test-notification-id"; + + @Test(expected = AppException.class) + public void should_throwException_whenSubscriptionNotFound() throws Exception { + when(this.subscriptionCacheFactory.get(any())).thenReturn(null); + ISubscriptionService subscriptionService = mock(SubscriptionService.class); + when(this.subscriptionFactory.create(any())).thenReturn(subscriptionService); + HttpResponse response = new HttpResponse(); + when(subscriptionService.query(any())).thenThrow(new SubscriptionException("error", response)); + this.sut.getSubscriptionFromCache(this.NOTIFICATION_ID); + fail("should throw AppException"); + } + + @Test(expected = AppException.class) + public void should_throwException_whenSubscriptionParsingErrorOccurs() throws Exception { + when(this.subscriptionCacheFactory.get(any())).thenReturn(null); + ISubscriptionService subscriptionService = mock(SubscriptionService.class); + when(this.subscriptionFactory.create(any())).thenReturn(subscriptionService); + String jsonSubscription = this.getHmacSubscription(); + ObjectMapper objectMapper = new ObjectMapper(); + Subscription subscription = objectMapper.readValue(jsonSubscription, Subscription.class); + List<Subscription> queryResult = new ArrayList<>(); + queryResult.add(subscription); + when(subscriptionService.query(any())).thenReturn(queryResult); + sut.setObjectMapper(this.objectMapper); + when(this.objectMapper.readValue(anyString(), any(Class.class))).thenThrow(new IOException()); + this.sut.getSubscriptionFromCache(NOTIFICATION_ID); + fail("should throw AppException"); + } + + @Test + public void should_return200_whenSubscriptionGotFromRegistration() throws Exception { + when(this.subscriptionCacheFactory.get(any())).thenReturn(null); + ISubscriptionService subscriptionService = mock(SubscriptionService.class); + when(this.subscriptionFactory.create(any())).thenReturn(subscriptionService); + sut.setObjectMapper(new ObjectMapper()); + String jsonSubscription = this.getHmacSubscription(); + ObjectMapper objectMapper = new ObjectMapper(); + Subscription subscription = objectMapper.readValue(jsonSubscription, Subscription.class); + List<Subscription> queryResult = new ArrayList<>(); + queryResult.add(subscription); + when(subscriptionService.query(any())).thenReturn(queryResult); + Subscription response = this.sut.getSubscriptionFromCache(NOTIFICATION_ID); + Assert.assertEquals("testSubscription", response.getName()); + } + + private String getHmacSubscription() { + return "{\n" + + "\t\"name\": \"testSubscription\",\n" + + "\t\"description\": \"Description\",\n" + + "\t\"topic\": \"records-changed\",\n" + + "\t\"pushEndpoint\": \"http://challenge\",\n" + + "\t\"notificationId\": \"notificationId\",\n" + + "\t\"id\": \"id_1\",\n" + + "\t\"createdBy\": \"test@test.com\",\n" + + "\t\"secret\": {\n" + + "\t\t\"secretType\": \"HMAC\",\n" + + "\t\t\"value\": \"testsecret\"\n" + + "\t}\n" + + "}"; + } +} diff --git a/pom.xml b/pom.xml index 4bd703019b2e183525b7a103262be38a46fa796a..f06e315558e4c86423f415e1f82873410511a079 100644 --- a/pom.xml +++ b/pom.xml @@ -18,14 +18,14 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <description>Root Notification Service project</description> <properties> <java.version>8</java.version> <maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.source>${java.version}</maven.compiler.source> - <os-core-common.version>0.6.9</os-core-common.version> + <os-core-common.version>0.11.0</os-core-common.version> </properties> <licenses> @@ -43,7 +43,7 @@ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> - <version>2.1.7.RELEASE</version> + <version>2.1.18.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> @@ -53,6 +53,24 @@ <artifactId>os-core-common</artifactId> <version>${os-core-common.version}</version> </dependency> + + <!-- overriding packages with known vulnerabilities --> + <!-- See: https://nvd.nist.gov/vuln/search/results?form_type=Advanced&results_type=overview&search_type=all&cpe_vendor=cpe%3A%2F%3Afasterxml&cpe_product=cpe%3A%2F%3Afasterxml%3Ajackson-databind&cpe_version=cpe%3A%2F%3Afasterxml%3Ajackson-databind%3A2.9.9--> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + <version>2.12.3</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.12.3</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + <version>2.12.3</version> + </dependency> </dependencies> </dependencyManagement> @@ -71,6 +89,7 @@ <module>provider/notification-azure</module> <module>provider/notification-ibm</module> <module>provider/notification-aws</module> + <module>provider/notification-reference</module> </modules> <repositories> @@ -110,4 +129,44 @@ </profile> </profiles> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <id>build-info</id> + <goals> + <goal>build-info</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </pluginManagement> + <plugins> + <plugin> + <groupId>pl.project13.maven</groupId> + <artifactId>git-commit-id-plugin</artifactId> + <version>4.0.5</version> + <executions> + <execution> + <goals> + <goal>revision</goal> + </goals> + </execution> + </executions> + <configuration> + <verbose>true</verbose> + <dateFormat>yyyy-MM-dd'T'HH:mm:ssZ</dateFormat> + <generateGitPropertiesFile>true</generateGitPropertiesFile> + <generateGitPropertiesFilename> + ${project.build.outputDirectory}/git.properties + </generateGitPropertiesFilename> + </configuration> + </plugin> + </plugins> + </build> </project> diff --git a/provider/notification-aws/build-aws/buildspec.yaml b/provider/notification-aws/build-aws/buildspec.yaml index e5fc2bd6ed3d59236c0bdfba4a0308f4c33302fb..6d134addb8b5e83b8f9b07b83d24b9586dd41090 100644 --- a/provider/notification-aws/build-aws/buildspec.yaml +++ b/provider/notification-aws/build-aws/buildspec.yaml @@ -21,6 +21,11 @@ env: secrets-manager: DOCKER_USERNAME: /osdu/devops/docker_credentials:username DOCKER_PASSWORD: /osdu/devops/docker_credentials:password + SONAR_USERNAME: /osdu/devops/sonar_credentials:username + SONAR_PASSWORD: /osdu/devops/sonar_credentials:password + + parameter-store: + SONAR_URL: /osdu/devops/sonar_url phases: install: @@ -58,7 +63,7 @@ phases: - printenv - echo "Building primary service assemblies..." - - mvn -ntp -B test install -pl notification-core,provider/notification-aws -Ddeployment.environment=prod + - mvn -ntp -B test install sonar:sonar -pl .,notification-core,provider/notification-aws -Ddeployment.environment=prod -Dsonar.login=${SONAR_USERNAME} -Dsonar.password=${SONAR_PASSWORD} -Dsonar.branch.name=${BRANCH_NAME} - echo "Building integration testing assemblies and gathering artifacts..." - ./testing/notification-test-aws/build-aws/prepare-dist.sh @@ -78,6 +83,9 @@ phases: python provider/notification-aws/build-aws/build-info.py --branch ${CODEBUILD_SOURCE_VERSION} --commit ${CODEBUILD_RESOLVED_SOURCE_VERSION} \ --buildid ${CODEBUILD_BUILD_ID} --buildnumber ${CODEBUILD_BUILD_NUMBER} --reponame ${REPO_NAME} --outdir ${OUTPUT_DIR} \ --artifact ${ECR_IMAGE} + post_build: + commands: + - cp provider/notification-aws/target/dependency-check-report.html ${OUTPUT_DIR} reports: SurefireReports: # CodeBuild will create a report group called "SurefireReports". files: #Store all of the files diff --git a/provider/notification-aws/maven/settings.xml b/provider/notification-aws/maven/settings.xml index 3dbde15f47c27d083537fa2f0b280f97f9aa54b5..3bd1cd4aa7ed183241e62e75d2b7f1a3f5ebc7aa 100644 --- a/provider/notification-aws/maven/settings.xml +++ b/provider/notification-aws/maven/settings.xml @@ -1,18 +1,19 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- - Copyright © 2020 Amazon Web Services - 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 +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.​ +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 + 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. - 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. --> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" @@ -53,6 +54,17 @@ <azure.devops.token>no-default</azure.devops.token> </properties> </profile> + <profile> + <id>sonar</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <properties> + <sonar.host.url> + ${env.SONAR_URL} + </sonar.host.url> + </properties> + </profile> </profiles> <servers> @@ -63,15 +75,14 @@ </server> </servers> - <!-- CodeArtifact doesn't support external repos yet that aren't Maven Central. ETA Q4 2020. --> - <!-- <mirrors> --> - <!-- <mirror> --> - <!-- <id>aws-osdu-dev-maven</id> --> - <!-- <name>aws-osdu-dev-maven</name> --> - <!-- <url>https://osdu-dev-888733619319.d.codeartifact.us-east-1.amazonaws.com/maven/osdu-maven/</url> --> - <!-- <mirrorOf>*,!gitlab-os-core-common-maven</mirrorOf> --> - <!-- </mirror> --> - <!-- </mirrors> --> + <mirrors> + <mirror> + <id>aws-osdu-dev-maven</id> + <name>aws-osdu-dev-maven</name> + <url>https://osdu-dev-${AWS_ACCOUNT_ID}.d.codeartifact.us-east-1.amazonaws.com/maven/osdu-maven/</url> + <mirrorOf>central,!gitlab-os-core-common-maven,!gitlab-os-core-lib-aws-maven</mirrorOf> + </mirror> + </mirrors> <activeProfiles> <activeProfile>credentialsConfiguration</activeProfile> diff --git a/provider/notification-aws/pom.xml b/provider/notification-aws/pom.xml index 55eb27000c6286ab2a4efa1083565df55292d3e4..ff238277d58b6aae0506a19021bb1f318c4c5c12 100644 --- a/provider/notification-aws/pom.xml +++ b/provider/notification-aws/pom.xml @@ -19,7 +19,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-aws</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-aws</name> <description>AWS implementation for Notification service</description> <packaging>jar</packaging> @@ -27,7 +27,7 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../../pom.xml</relativePath> </parent> @@ -35,20 +35,14 @@ <java.version>8</java.version> <maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.source>${java.version}</maven.compiler.source> - <aws.version>1.11.637</aws.version> + <aws.version>1.11.1018</aws.version> </properties> <dependencies> - <!-- <dependency> - <groupId>org.opengroup.osdu</groupId> - <artifactId>os-core-common</artifactId> - </dependency> --> - - <dependency> <groupId>org.opengroup.osdu.core.aws</groupId> <artifactId>os-core-lib-aws</artifactId> - <version>0.9.1-SNAPSHOT</version> + <version>0.11.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.amazonaws/aws-java-sdk-secretsmanager --> @@ -61,7 +55,7 @@ <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> </dependency> <dependency> @@ -94,6 +88,7 @@ <version>4.12</version> <scope>test</scope> </dependency> + </dependencies> <build> @@ -121,6 +116,18 @@ <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> + <plugin> + <groupId>org.owasp</groupId> + <artifactId>dependency-check-maven</artifactId> + <version>6.2.2</version> + <executions> + <execution> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> </build> </project> diff --git a/provider/notification-aws/src/main/resources/application.properties b/provider/notification-aws/src/main/resources/application.properties index 704966d16d904a03b2c712057b97a0772d96bdac..476714e0d2e4c37fee01a61125079f30fdcf454b 100644 --- a/provider/notification-aws/src/main/resources/application.properties +++ b/provider/notification-aws/src/main/resources/application.properties @@ -19,7 +19,7 @@ server.port=${APPLICATION_PORT:8080} AUTHORIZE_API=${ENTITLEMENTS_BASE_URL}/api/entitlements/v2 PARTITION_API=${ENTITLEMENTS_BASE_URL}/api/partition/v1 -REGISTER_SERVICE_URL=${ENTITLEMENTS_BASE_URL}/api/register/v1 +REGISTER_SERVICE_URL=${REGISTER_BASE_URL}/api/register/v1 aws.ssm=${SSM_ENABLED:True} aws.environment=${RESOURCE_PREFIX} @@ -48,3 +48,5 @@ server.ssl.key-store=${SSL_KEY_STORE_PATH:/certs/osduonaws.p12} server.ssl.key-alias=${SSL_KEY_ALIAS:osduonaws} server.ssl.key-password=${SSL_KEY_PASSWORD:} server.ssl.key-store-password=${SSL_KEY_STORE_PASSWORD:} + +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration \ No newline at end of file diff --git a/provider/notification-azure/GUIDELINES_FOR_USING_NOTIFICATION.md b/provider/notification-azure/docs/GUIDELINES_FOR_USING_NOTIFICATION.md similarity index 100% rename from provider/notification-azure/GUIDELINES_FOR_USING_NOTIFICATION.md rename to provider/notification-azure/docs/GUIDELINES_FOR_USING_NOTIFICATION.md diff --git a/provider/notification-azure/docs/MIGRATION.md b/provider/notification-azure/docs/MIGRATION.md new file mode 100644 index 0000000000000000000000000000000000000000..db89d26264619f07288f50e927aa147213183ef8 --- /dev/null +++ b/provider/notification-azure/docs/MIGRATION.md @@ -0,0 +1,85 @@ +## Introduction + +The document talks about the plan to move from Event Grid to Service Bus. The major consumer of the same iss +Notification Service. As Notification Service is consumed by external customers, so a clean migration path is needed. + +## Goals + +The Migration must happen respecting the following + +1. No re-registration +2. Zero downtime. +3. No notification loss. + +#### Prerequisites + +1. Please verify that the topic you want to use exists. If not, + follow [this](https://community.opengroup.org/osdu/platform/system/notification/-/blob/master/provider/notification-azure/PLAYBOOK_FOR_TOPIC_CREATION.md) + guide to create one. +2. Install + the [latest version](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.1) + of PowerShell available for your operating system. + +## How to do the migration + +Migration is something which must be performed by SRE. The flags must be switched in a sequence.The flags are controlled +by deployment.yaml for the service. Following are the steps needed for successful migration. + +1. Have 2 deployment of Notification Service running before migration: + 1. Deployment 1,having event_grid_enabled. + 2. Deployment 2,having service_bus_enabled.The consecutive_subscription_manager_delay_seconds should not be set much + higher as it will pile up alot of SB messages because of the longer time of listening new subscribers on the fly. + **The number of pods for both the deployments should be managed as per the load during migration** +2. Get list of subscriptions per partition and prepare migrationConfig.json [Manual-from azure portal]: +3. Complete powershell Az module installation and Azure login by running script + - [migrationSetup.ps1](https://community.opengroup.org/osdu/platform/system/notification/-/blob/master/provider/notification-azure/src/main/resources/migrationSetup.ps1) + . + +**Note:The Azure Az PowerShell module works with PowerShell 7.x and later on all platforms.To check your PowerShell +version, run the following command from within a PowerShell session:$PSVersionTable.PSVersion** + +4. Create the subscribers on Service Bus with the same notification id as in CosmosDB by providing migrationConfig.json + to the + script-[migrationToServiceBus.ps1](https://community.opengroup.org/osdu/platform/system/notification/-/blob/master/provider/notification-azure/src/main/resources/migrationToServiceBus.ps1) + . All the subscriptions from one Event grid topic will be moved to service bus topic for one execution of above + mentioned script.We need to rerun script for multiple topic across multiple partitions. +5. Turn on **Service bus flags** and turn off **Event Grid flags** in Producer Services. For example Storage service + publishes to Service bus and Event grid both as of now. During migration,while we are moving to Service bus + completely we have to stop publishing to Event Grid as a part of migration, so we can eventually disable it and move + to Service Bus completely.Same applies to other producer services like -Legal, Schema + etc. [Manual- Configuring deployment.yaml for Legal, Schema, Storage Services] +6. Wait for Event Grid subscribers to drain.[Manual-from azure portal] + **Note: If there are delivery failures,then retry can occur after a longer duration,EG should be kept enabled for + sufficiently long time** +7. Turn on **Service bus flags** in Register Service.[Manual] +8. Event Grid Subscriber clean up.[Manual-from azure portal] + +The 3rd and 4th steps would be performed by the scripts in the order given below : +** Powershell must be elevated to admin.** + +1. [migrationSetup.ps1](https://community.opengroup.org/osdu/platform/system/notification/-/blob/master/provider/notification-azure/src/main/resources/migrationSetup.ps1) +2. [migrationToServiceBus.ps1](https://community.opengroup.org/osdu/platform/system/notification/-/blob/master/provider/notification-azure/src/main/resources/migrationToServiceBus.ps1) + +## List of Feature Flags to be used from deployment.yaml of respective services + +1. Notification Service + 1. service_bus_enabled + 2. event_grid_enabled +2. Register Service + 1. azure_serviceBus_enabled + 2. azure_eventGrid_enabled +3. Producer Services + 1. Storage Service : azure_publishToEventGrid + 2. Legal Service : azure.publishToEventGrid + 3. Schema Service : event_grid_enabled + +## Post Migration Plan + +1. Redeploy Notification Service to have only 1 deployment with service_bus_enabled set to true and event_grid_enabled + set to false.The number of pods can be managed as per the load + +## Note + +There would be duplication of notifications that accounts for the time when producer services will be publishing to both +Service bus and Event grid at any time during migration.For example Storage service by default publish to both.We have +to use Publish flags carefully to avoid duplication \ No newline at end of file diff --git a/provider/notification-azure/PLAYBOOK_FOR_TOPIC_CREATION.md b/provider/notification-azure/docs/PLAYBOOK_FOR_TOPIC_CREATION.md similarity index 100% rename from provider/notification-azure/PLAYBOOK_FOR_TOPIC_CREATION.md rename to provider/notification-azure/docs/PLAYBOOK_FOR_TOPIC_CREATION.md diff --git a/provider/notification-azure/pom.xml b/provider/notification-azure/pom.xml index 40f6b48feed203ace47de72652ace01ad81f12a0..5a324bcada1460417a3ccfbd9e60f3db49e72d6a 100644 --- a/provider/notification-azure/pom.xml +++ b/provider/notification-azure/pom.xml @@ -18,7 +18,7 @@ <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>notification-azure</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-azure</name> <description>Azure implementation for Notification service</description> <packaging>jar</packaging> @@ -26,7 +26,7 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../../pom.xml</relativePath> </parent> @@ -35,18 +35,30 @@ <maven.compiler.target>${java.version}</maven.compiler.target> <maven.compiler.source>${java.version}</maven.compiler.source> <jacoco-maven-plugin.version>0.8.2</jacoco-maven-plugin.version> - <osdu.notification-core.version>0.9.0-SNAPSHOT</osdu.notification-core.version> + <osdu.notification-core.version>0.12.0-SNAPSHOT</osdu.notification-core.version> <springframework.version>4.3.0.RELEASE</springframework.version> - <reactor.netty.version>0.9.0.RELEASE</reactor.netty.version> + <reactor.netty.version>0.11.0.RELEASE</reactor.netty.version> <reactor.core.version>3.3.0.RELEASE</reactor.core.version> - <osdu.corelibazure.version>0.0.66</osdu.corelibazure.version> + <osdu.corelibazure.version>0.11.0</osdu.corelibazure.version> <junit.version>5.6.0</junit.version> <jjwt.version>3.8.1</jjwt.version> <mockito.version>2.23.0</mockito.version> + <spring-boot.version>2.1.18.RELEASE</spring-boot.version> + <jackson.version>2.11.4</jackson.version> + <reactor-core.version>3.4.6</reactor-core.version> + <reactor-netty.version>1.0.7</reactor-netty.version> + <oauth2-oidc-sdk.version>6.0</oauth2-oidc-sdk.version> </properties> <dependencyManagement> <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>${spring-boot.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> <!-- Inherit managed dependencies from core-lib-azure --> <dependency> <groupId>org.opengroup.osdu</groupId> @@ -91,11 +103,45 @@ </exclusions> </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + <version>${jackson.version}</version> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>${jackson.version}</version> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.dataformat</groupId> + <artifactId>jackson-dataformat-xml</artifactId> + <version>${jackson.version}</version> + </dependency> + + <dependency> + <groupId>io.projectreactor.netty</groupId> + <artifactId>reactor-netty</artifactId> + <version>${reactor-netty.version}</version> + </dependency> + <dependency> + <groupId>io.projectreactor</groupId> + <artifactId>reactor-core</artifactId> + <version>${reactor-core.version}</version> + </dependency> + <dependency> + <groupId>com.nimbusds</groupId> + <artifactId>oauth2-oidc-sdk</artifactId> + <version>${oauth2-oidc-sdk.version}</version> + </dependency> + <!-- Azure Dependencies --> <dependency> - <groupId>com.microsoft.azure</groupId> - <artifactId>azure-active-directory-spring-boot-starter</artifactId> + <groupId>com.azure.spring</groupId> + <artifactId>azure-spring-boot-starter-active-directory</artifactId> </dependency> <!-- Test Dependencies --> @@ -116,6 +162,11 @@ <version>${junit.version}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> @@ -163,7 +214,6 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> - <version>2.4.2</version> <configuration> <useSystemClassLoader>false</useSystemClassLoader> <threadCount>1</threadCount> diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/Application.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/Application.java index 79a9ffdb69a44139abcefd6ad4f0b292256ca62f..c9f2e556dc0a4d03d5b936907a21be12b716267e 100644 --- a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/Application.java +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/Application.java @@ -14,18 +14,48 @@ package org.opengroup.osdu.notification.provider.azure; +import org.opengroup.osdu.notification.provider.azure.messageBus.interfaces.ISubscriptionManager; +import org.opengroup.osdu.notification.provider.azure.messageBus.thread.ThreadScopeBeanFactoryPostProcessor; +import org.opengroup.osdu.notification.provider.azure.util.AzureServiceBusConfig; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableAsync; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + @SpringBootApplication @ComponentScan({"org.opengroup.osdu"}) @EnableAsync public class Application { public static void main(String[] args) { - SpringApplication.run(new Class[]{Application.class}, args); + + ApplicationContext context = SpringApplication.run(new Class[]{Application.class}, args); + // Subscribe To Notification Event for Service Bus Notification Processing + AzureServiceBusConfig azureServiceBusConfig = context.getBean(AzureServiceBusConfig.class); + if (Boolean.parseBoolean(azureServiceBusConfig.getServiceBusEnabled())) { + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + ISubscriptionManager subscriptionManager = context.getBean(ISubscriptionManager.class); + /* + Here the initialSubscriptionManagerDelay is used to have a delay before the first execution. + Every consecutive execution will take place after a delay of consecutiveSubscriptionManagerDelay. + If any of the execution exceeds the time consecutiveSubscriptionManagerDelay then next execution + will begin immediately after the current execution is completed. + */ + executorService.scheduleAtFixedRate(subscriptionManager, Integer.parseUnsignedInt(azureServiceBusConfig.getInitialSubscriptionManagerDelay()), + Integer.parseUnsignedInt(azureServiceBusConfig.getConsecutiveSubscriptionManagerDelay()), TimeUnit.SECONDS); + } + } + + @Bean + public static BeanFactoryPostProcessor beanFactoryPostProcessor() { + return new ThreadScopeBeanFactoryPostProcessor(); } } diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/MessageHandler.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/MessageHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..bef7ffd10d50865a05591beaa13e90394ff9c882 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/MessageHandler.java @@ -0,0 +1,52 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus; + +import com.microsoft.azure.servicebus.ExceptionPhase; +import com.microsoft.azure.servicebus.IMessage; +import com.microsoft.azure.servicebus.IMessageHandler; +import com.microsoft.azure.servicebus.SubscriptionClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; + +public class MessageHandler implements IMessageHandler { + + private final static Logger LOGGER = LoggerFactory.getLogger(MessageHandler.class); + private final SubscriptionClient receiveClient; + private ProcessNotification processNotification; + + public MessageHandler(SubscriptionClient client, ProcessNotification processNotification) { + this.receiveClient = client; + this.processNotification = processNotification; + } + + @Override + public CompletableFuture<Void> onMessageAsync(IMessage message) { + try { + this.processNotification.performNotification(message, receiveClient.getSubscriptionName()); + return this.receiveClient.completeAsync(message.getLockToken()); + } catch (Exception e) { + LOGGER.error("Unable to process the Notification : " + e); + return this.receiveClient.abandonAsync(message.getLockToken()); + } + } + + @Override + public void notifyException(Throwable throwable, ExceptionPhase exceptionPhase) { + LOGGER.error("{} - {}", exceptionPhase, throwable.getMessage()); + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/ProcessNotification.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/ProcessNotification.java new file mode 100644 index 0000000000000000000000000000000000000000..e33e641f59ce8c0caa222c77574f5376e124e388 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/ProcessNotification.java @@ -0,0 +1,72 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus; + +import com.microsoft.azure.servicebus.IMessage; +import org.opengroup.osdu.core.common.http.HttpResponse; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.notification.provider.azure.messageBus.thread.ThreadScopeContextHolder; +import org.opengroup.osdu.notification.provider.azure.models.NotificationContent; +import org.opengroup.osdu.notification.provider.azure.messageBus.extractor.RequestBodyAdapter; +import org.opengroup.osdu.notification.provider.azure.messageBus.thread.ThreadDpsHeaders; +import org.opengroup.osdu.notification.provider.azure.util.MDCContextMap; +import org.opengroup.osdu.notification.service.NotificationHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnExpression("${azure.serviceBus.enabled:true} || ${azure.eventGridToServiceBus.enabled:true}") +public class ProcessNotification { + private final String NOT_ACKNOWLEDGE = "message not acknowledged by client"; + private final static Logger LOGGER = LoggerFactory.getLogger(ProcessNotification.class); + @Autowired + private NotificationHandler notificationHandler; + @Autowired + private RequestBodyAdapter requestBodyAdapter; + @Autowired + private ThreadDpsHeaders dpsHeaders; + @Autowired + private MDCContextMap mdcContextMap; + + public void performNotification(IMessage message, String subscriptionName) throws Exception { + try { + NotificationContent notificationContent = requestBodyAdapter.extractNotificationContent(message, subscriptionName); + + String dataPartitionId = notificationContent.getExtractAttributes().get(DpsHeaders.DATA_PARTITION_ID); + String correlationId = notificationContent.getExtractAttributes().get(DpsHeaders.CORRELATION_ID); + + MDC.setContextMap(mdcContextMap.getContextMap(correlationId, dataPartitionId)); + dpsHeaders.setThreadContext(dataPartitionId, correlationId); + + LOGGER.info("Notification process started for message with id: {}", message.getMessageId()); + + HttpResponse response = notificationHandler.notifySubscriber(notificationContent.getNotificationId(), + notificationContent.getData(), notificationContent.getExtractAttributes()); + if (!response.isSuccessCode()) { + throw new Exception(NOT_ACKNOWLEDGE); + } + } catch (Exception e) { + LOGGER.error(String.format("An error occurred performing Notification for message with ID: ", message.getMessageId()), e); + throw e; + } finally { + ThreadScopeContextHolder.getContext().clear(); + MDC.clear(); + } + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/SubscriptionClientFactImpl.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/SubscriptionClientFactImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..a42ca37f435a0864f5c43e5d6a412d077cf7ffbb --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/SubscriptionClientFactImpl.java @@ -0,0 +1,42 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus; + +import com.microsoft.azure.servicebus.SubscriptionClient; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; +import org.opengroup.osdu.azure.servicebus.ISubscriptionClientFactory; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnExpression("${azure.serviceBus.enabled:true} || ${azure.eventGridToServiceBus.enabled:true}") +public class SubscriptionClientFactImpl { + private final static Logger LOGGER = LoggerFactory.getLogger(SubscriptionClientFactImpl.class); + @Autowired + private ISubscriptionClientFactory subscriptionClientFactory; + + public SubscriptionClient getSubscriptionClient(String dataPartition, String sbTopic, String sbSubscription) throws ServiceBusException, InterruptedException { + try { + return subscriptionClientFactory.getClient(dataPartition, sbTopic, sbSubscription); + } catch (ServiceBusException | InterruptedException e) { + LOGGER.error("Unexpected error creating Subscription Client", e); + throw new AppException(500, "Server Error", "Unexpected error creating Subscription Client", e); + } + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/SubscriptionManagerImpl.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/SubscriptionManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..9edeecf9f76f048e289361c2b47a0439d587aa36 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/SubscriptionManagerImpl.java @@ -0,0 +1,139 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus; + +import com.microsoft.azure.servicebus.MessageHandlerOptions; +import com.microsoft.azure.servicebus.SubscriptionClient; +import com.microsoft.azure.servicebus.management.ManagementClient; +import com.microsoft.azure.servicebus.primitives.ConnectionStringBuilder; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; +import org.opengroup.osdu.azure.cosmosdb.CosmosStore; +import org.opengroup.osdu.azure.partition.PartitionInfoAzure; +import org.opengroup.osdu.azure.partition.PartitionServiceClient; +import org.opengroup.osdu.azure.serviceBusManager.IManagementClientFactory; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.notification.Subscription; +import org.opengroup.osdu.core.common.model.tenant.TenantInfo; +import org.opengroup.osdu.core.common.notification.ISubscriptionFactory; +import org.opengroup.osdu.core.common.provider.interfaces.ITenantFactory; +import org.opengroup.osdu.notification.provider.azure.messageBus.interfaces.ISubscriptionManager; +import org.opengroup.osdu.notification.provider.azure.messageBus.models.TopicSubscriptions; +import org.opengroup.osdu.notification.provider.azure.util.AzureServiceBusConfig; +import org.opengroup.osdu.notification.provider.azure.util.AzureCosmosProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +@Component +@ConditionalOnExpression("${azure.serviceBus.enabled:true} || ${azure.eventGridToServiceBus.enabled:true}") +public class SubscriptionManagerImpl implements ISubscriptionManager { + private final static Logger LOGGER = LoggerFactory.getLogger(SubscriptionManagerImpl.class); + @Autowired + private TopicSubscriptions topicSubscriptions; + @Autowired + private SubscriptionClientFactImpl subscriptionClientFactory; + @Autowired + private ProcessNotification processNotification; + @Autowired + private ITenantFactory tenantFactory; + @Autowired + private ISubscriptionFactory subscriptionFactory; + @Autowired + private PartitionServiceClient partitionService; + @Autowired + private CosmosStore cosmosStore; + @Autowired + private AzureCosmosProperties azureCosmosProperties; + @Autowired + private AzureServiceBusConfig azureServiceBusConfig; + @Autowired + private IManagementClientFactory factory; + + @Override + public void subscribeNotificationsEvent() { + + List<String> tenantList = tenantFactory.listTenantInfo().stream().map(TenantInfo::getDataPartitionId) + .collect(Collectors.toList()); + ExecutorService executorService = Executors + .newFixedThreadPool(Integer.parseUnsignedInt(azureServiceBusConfig.getNThreads())); + for (String partition : tenantList) { + try { + List<Subscription> subscriptionsList = cosmosStore.findAllItems(partition, azureCosmosProperties.cosmosDBName(), + azureCosmosProperties.registerSubscriptionContainerName(), Subscription.class); + + ManagementClient managementClient = factory.getManager(partition); + for (Subscription subscription : subscriptionsList) { + // To check if its a not new subscription. + if (!this.topicSubscriptions.checkIfNewTopicSubscription(partition, subscription.getTopic(), subscription.getNotificationId())) { + // Update existing subscriptions and skip registration + this.topicSubscriptions.updateCurrentTopicSubscriptions(partition, subscription.getTopic(), subscription.getNotificationId()); + + } else { + + /* This check is added if a Cosmos subscription is created but the corresponding service bus + subscription is still not created or creation is in progress.We do not register message handler + with the subscription client as it will throw entity not found exception and unregistering is not supported. + Check if its a new Subscription Client */ + if (managementClient.topicExists(subscription.getTopic()) && managementClient.subscriptionExists(subscription.getTopic(), subscription.getNotificationId())) { + try { + SubscriptionClient subscriptionClient = this.subscriptionClientFactory + .getSubscriptionClient(partition, subscription.getTopic(), subscription.getNotificationId()); + registerMessageHandler(subscriptionClient, executorService); + this.topicSubscriptions.updateCurrentTopicSubscriptions(partition, subscription.getTopic(), subscription.getNotificationId()); + } catch (InterruptedException | ServiceBusException e) { + LOGGER.error("Error while creating or registering subscription client {}", e.getMessage(), e); + } catch (Exception e) { + LOGGER.error("Unknown exception occurred while creating or registering subscription client: ", e); + } + } + } + } + + } catch (AppException e) { + LOGGER.error("Error creating Cosmos Client {}", e.getMessage(), e); + } catch (Exception e) { + LOGGER.error("An exception occurred while subscribing to Notification Event : ", e); + } + } + this.topicSubscriptions.clearTopicSubscriptions(); + + } + + private void registerMessageHandler(SubscriptionClient subscriptionClient, ExecutorService executorService) throws ServiceBusException, InterruptedException { + + MessageHandler messageHandler = new MessageHandler(subscriptionClient, processNotification); + subscriptionClient.registerMessageHandler( + messageHandler, + new MessageHandlerOptions(Integer.parseUnsignedInt(azureServiceBusConfig.getMaxConcurrentCalls()), + false, + Duration.ofSeconds(Integer.parseUnsignedInt(azureServiceBusConfig.getMaxLockRenewDurationInSeconds())), + Duration.ofSeconds(1) + ), + executorService); + } + + @Override + public void run() { + subscribeNotificationsEvent(); + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/EventGridServiceBusRequestBodyExtractor.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/EventGridServiceBusRequestBodyExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..cac95d9777a48303350beb26c87200fc80e4a6af --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/EventGridServiceBusRequestBodyExtractor.java @@ -0,0 +1,97 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.extractor; + +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import com.microsoft.azure.servicebus.IMessage; +import lombok.SneakyThrows; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.notification.provider.azure.models.NotificationEventGridServiceBusRequest; +import org.opengroup.osdu.notification.provider.azure.models.NotificationRecordsChangedData; +import org.opengroup.osdu.notification.provider.azure.messageBus.interfaces.IPullRequestBodyExtractor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@Component +@Scope(value = "ThreadScope", proxyMode = ScopedProxyMode.TARGET_CLASS) +@ConditionalOnProperty(value = "azure.eventGridToServiceBus.enabled", havingValue = "true", matchIfMissing = false) +public class EventGridServiceBusRequestBodyExtractor implements IPullRequestBodyExtractor { + private static final Gson GSON = new Gson(); + private NotificationEventGridServiceBusRequest notificationRequest; + private NotificationRecordsChangedData notificationRecordsChangedData; + private IMessage message; + + public void InitializeExtractor(IMessage message) { + + this.message = message; + this.notificationRequest = extractNotificationRequestFromMessageBody(); + } + + public Map<String, String> extractAttributesFromRequestBody() { + + Map<String, String> attributes = new HashMap<>(); + attributes.put("correlation-id", this.notificationRecordsChangedData.getCorrelationId()); + attributes.put("data-partition-id", this.notificationRecordsChangedData.getDataPartitionId()); + attributes.put("account-id", this.notificationRecordsChangedData.getAccountId()); + return attributes; + } + + public String extractDataFromRequestBody() { + + return notificationRecordsChangedData.getData().toString(); + } + + @SneakyThrows + private NotificationEventGridServiceBusRequest extractNotificationRequestFromMessageBody() { + + NotificationEventGridServiceBusRequest notificationRequest = null; + try { + String requestBody = new String(message.getMessageBody().getBinaryData().get(0), UTF_8); + NotificationEventGridServiceBusRequest[] notificationRequestArray = GSON.fromJson(requestBody, NotificationEventGridServiceBusRequest[].class); + notificationRequest = notificationRequestArray[0]; + extractNotificationData(notificationRequest); + } catch (Exception e) { + throw new AppException(HttpStatus.BAD_REQUEST.value(), "Request payload parsing error", + "Unable to parse request payload.", "Request contents are null or empty", e); + } + return notificationRequest; + } + + private void extractNotificationData(NotificationEventGridServiceBusRequest notificationRequest) { + + String notifData = notificationRequest.getData().toString(); + NotificationRecordsChangedData notificationRecordsChangedData = GSON.fromJson(notifData, NotificationRecordsChangedData.class); + verifyNotificationData(notificationRecordsChangedData); + this.notificationRecordsChangedData = notificationRecordsChangedData; + } + + private void verifyNotificationData(NotificationRecordsChangedData notificationRecordsChangedData) { + + Preconditions.checkNotNull(notificationRecordsChangedData, "Request payload parsing error"); + Preconditions.checkNotNull(notificationRecordsChangedData.getData(), "Request payload parsing error"); + Preconditions.checkNotNull(notificationRecordsChangedData.getCorrelationId(), "Request payload parsing error"); + Preconditions.checkNotNull(notificationRecordsChangedData.getDataPartitionId(), "Request payload parsing error"); + } +} + diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/RequestBodyAdapter.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/RequestBodyAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..4c1d9db4b231966ec112249f1cf8915f2ddb5c51 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/RequestBodyAdapter.java @@ -0,0 +1,40 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.extractor; + +import com.microsoft.azure.servicebus.IMessage; +import org.opengroup.osdu.notification.provider.azure.models.NotificationContent; +import org.opengroup.osdu.notification.provider.azure.messageBus.interfaces.IPullRequestBodyExtractor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +@ConditionalOnExpression("${azure.serviceBus.enabled:true} || ${azure.eventGridToServiceBus.enabled:true}") +public class RequestBodyAdapter { + @Autowired + IPullRequestBodyExtractor extractor; + + public NotificationContent extractNotificationContent(IMessage message, String subscriptionName) { + + extractor.InitializeExtractor(message); + String notificationData = extractor.extractDataFromRequestBody(); + Map<String, String> headerAttributes = extractor.extractAttributesFromRequestBody(); + NotificationContent notificationContent = NotificationContent.mapFrom(subscriptionName, notificationData, headerAttributes, false); + return notificationContent; + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/ServiceBusRequestBodyExtractor.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/ServiceBusRequestBodyExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..070029c6be6fb7e2f73c50636d0047517fdaf57d --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/extractor/ServiceBusRequestBodyExtractor.java @@ -0,0 +1,95 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.extractor; + +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import com.microsoft.azure.servicebus.IMessage; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.notification.provider.azure.models.NotificationRecordsChangedData; +import org.opengroup.osdu.notification.provider.azure.models.NotificationServiceBusRequest; +import org.opengroup.osdu.notification.provider.azure.messageBus.interfaces.IPullRequestBodyExtractor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@Component +@Scope(value = "ThreadScope", proxyMode = ScopedProxyMode.TARGET_CLASS) +@ConditionalOnProperty(value = "azure.serviceBus.enabled", havingValue = "true", matchIfMissing = false) +public class ServiceBusRequestBodyExtractor implements IPullRequestBodyExtractor { + private IMessage message; + private static final Gson GSON = new Gson(); + private NotificationServiceBusRequest notificationRequest; + private NotificationRecordsChangedData notificationRecordsChangedData; + + public void InitializeExtractor(IMessage message) { + + this.message = message; + this.notificationRequest = extractNotificationRequestFromMessageBody(); + } + + public Map<String, String> extractAttributesFromRequestBody() { + + Map<String, String> attributes = new HashMap<>(); + attributes.put("correlation-id", this.notificationRecordsChangedData.getCorrelationId()); + attributes.put("data-partition-id", this.notificationRecordsChangedData.getDataPartitionId()); + attributes.put("account-id", this.notificationRecordsChangedData.getAccountId()); + return attributes; + } + + public String extractDataFromRequestBody() { + return notificationRecordsChangedData.getData().toString(); + } + + private NotificationServiceBusRequest extractNotificationRequestFromMessageBody() { + + NotificationServiceBusRequest notificationRequest = null; + try { + String requestBody = new String(message.getMessageBody().getBinaryData().get(0), UTF_8); + NotificationServiceBusRequest notificationRequestArray = GSON.fromJson(requestBody, NotificationServiceBusRequest.class); + notificationRequest = notificationRequestArray; + extractNotificationData(notificationRequest); + } catch (Exception e) { + throw new AppException(HttpStatus.BAD_REQUEST.value(), "Request payload parsing error", + "Unable to parse request payload.", "Request contents are null or empty", e); + } + return notificationRequest; + + } + + private void extractNotificationData(NotificationServiceBusRequest notificationRequest) { + + String notifData = notificationRequest.getMessage().toString(); + NotificationRecordsChangedData notificationRecordsChangedData = GSON.fromJson(notifData, NotificationRecordsChangedData.class); + verifyNotificationData(notificationRecordsChangedData); + this.notificationRecordsChangedData = notificationRecordsChangedData; + } + + private void verifyNotificationData(NotificationRecordsChangedData notificationRecordsChangedData) { + + Preconditions.checkNotNull(notificationRecordsChangedData, "Request payload parsing error"); + Preconditions.checkNotNull(notificationRecordsChangedData.getData(), "Request payload parsing error"); + Preconditions.checkNotNull(notificationRecordsChangedData.getCorrelationId(), "Request payload parsing error"); + Preconditions.checkNotNull(notificationRecordsChangedData.getDataPartitionId(), "Request payload parsing error"); + } + +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/interfaces/IPullRequestBodyExtractor.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/interfaces/IPullRequestBodyExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..bea354845a940a7897f4e4f97d311d969c8d7e3c --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/interfaces/IPullRequestBodyExtractor.java @@ -0,0 +1,27 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.interfaces; + +import com.microsoft.azure.servicebus.IMessage; + +import java.util.Map; + +public interface IPullRequestBodyExtractor { + void InitializeExtractor(IMessage message); + + Map<String, String> extractAttributesFromRequestBody(); + + String extractDataFromRequestBody(); +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/interfaces/ISubscriptionManager.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/interfaces/ISubscriptionManager.java new file mode 100644 index 0000000000000000000000000000000000000000..8c8e726c4351a26fc38b0aff842b8b728a9274e9 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/interfaces/ISubscriptionManager.java @@ -0,0 +1,19 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.interfaces; + +public interface ISubscriptionManager extends Runnable { + void subscribeNotificationsEvent(); +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/models/TopicSubscriptions.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/models/TopicSubscriptions.java new file mode 100644 index 0000000000000000000000000000000000000000..92eb9abbaf015c5605d4990eece4ab00b13070f7 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/models/TopicSubscriptions.java @@ -0,0 +1,52 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.models; + +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +public class TopicSubscriptions { + + private Map<String, Map<String, List<String>>> existingTopicSubscriptions = new HashMap<>(); + private Map<String, Map<String, List<String>>> currentTopicSubscriptions = new HashMap<>(); + + public boolean checkIfNewTopicSubscription(String partition, String sbTopicName, String subscriptionName) { + + if (existingTopicSubscriptions.get(partition) == null || + existingTopicSubscriptions.get(partition).get(sbTopicName) == null || + !existingTopicSubscriptions.get(partition).get(sbTopicName).contains(subscriptionName)) + return true; + return false; + } + + public void updateCurrentTopicSubscriptions(String partition, String sbTopicName, String subscriptionName) { + // Update the active subscriptions + currentTopicSubscriptions.putIfAbsent(partition, new HashMap<String, List<String>>()); + currentTopicSubscriptions.get(partition).putIfAbsent(sbTopicName, new ArrayList<>()); + currentTopicSubscriptions.get(partition).get(sbTopicName).add(subscriptionName); + } + + public void clearTopicSubscriptions() { + // Deletes the old subscriptions for next run + this.existingTopicSubscriptions.clear(); + this.existingTopicSubscriptions.putAll(this.currentTopicSubscriptions); + this.currentTopicSubscriptions.clear(); + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadDpsHeaders.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadDpsHeaders.java new file mode 100644 index 0000000000000000000000000000000000000000..b02fe38df5244618ca5cd0f9657c571780874598 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadDpsHeaders.java @@ -0,0 +1,48 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.thread; + +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.util.IServiceAccountJwtClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +@Scope(value = "ThreadScope", proxyMode = ScopedProxyMode.TARGET_CLASS) +@ConditionalOnExpression("${azure.serviceBus.enabled:true} || ${azure.eventGridToServiceBus.enabled:true}") +@Primary +public class ThreadDpsHeaders extends DpsHeaders { + @Autowired + private IServiceAccountJwtClient serviceAccountJwtClient; + + public void setThreadContext(String dataPartitionId, String correlationId) { + Map<String, String> headers = new HashMap<>(); + headers.put(DpsHeaders.DATA_PARTITION_ID, dataPartitionId); + headers.put(DpsHeaders.CORRELATION_ID, correlationId); + String authToken = this.serviceAccountJwtClient.getIdToken(dataPartitionId); + headers.put(DpsHeaders.AUTHORIZATION, authToken); + this.addFromMap(headers); + } + +} + + diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScope.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScope.java new file mode 100644 index 0000000000000000000000000000000000000000..b3445775194f3986d7fbb2e7e71696789a4ad0c1 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScope.java @@ -0,0 +1,75 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.thread; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.Scope; + +/** + * Thread scope which allows putting data in thread scope and clearing up afterwards. + */ + +public class ThreadScope implements Scope, DisposableBean { + + /** + * Get bean for given name in the "ThreadScope". + */ + public Object get(String name, ObjectFactory<?> factory) { + ThreadScopeContext context = ThreadScopeContextHolder.getContext(); + + Object result = context.getBean(name); + if (null == result) { + result = factory.getObject(); + context.setBean(name, result); + } + return result; + } + + /** + * Removes bean from scope. + */ + public Object remove(String name) { + ThreadScopeContext context = ThreadScopeContextHolder.getContext(); + return context.remove(name); + } + + public void registerDestructionCallback(String name, Runnable callback) { + ThreadScopeContextHolder.getContext().registerDestructionCallback(name, callback); + } + + /** + * Resolve the contextual object for the given key, if any. E.g. the HttpServletRequest object for key "request". + */ + public Object resolveContextualObject(String key) { + return null; + } + + /** + * Return the conversation ID for the current underlying scope, if any. + * <p/> + * In this case, it returns the thread name. + */ + public String getConversationId() { + return Thread.currentThread().getName(); + } + + @Override + public void destroy() { + ThreadScopeContextHolder.clearContext(); + } +} + + diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeBeanFactoryPostProcessor.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeBeanFactoryPostProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..33ddea59efbc68b1f52d46b9f0ff577c83f075f2 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeBeanFactoryPostProcessor.java @@ -0,0 +1,26 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.thread; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +public class ThreadScopeBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) { + factory.registerScope("ThreadScope", new ThreadScope()); + } +} \ No newline at end of file diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeContext.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeContext.java new file mode 100644 index 0000000000000000000000000000000000000000..74acaebec47dacb75517075842092a40d2d74a07 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeContext.java @@ -0,0 +1,109 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.thread; + +import java.util.HashMap; +import java.util.Map; + +public class ThreadScopeContext { + + protected final Map<String, Bean> beans = new HashMap<>(); + + /** + * Get a bean value from the context. + * + * @param name bean name + * @return bean value or null + */ + public Object getBean(String name) { + Bean bean = beans.get(name); + if (null == bean) { + return null; + } + return bean.object; + } + + /** + * Set a bean in the context. + * + * @param name bean name + * @param object bean value + */ + public void setBean(String name, Object object) { + + Bean bean = beans.computeIfAbsent(name,k-> new Bean()); + bean.object = object; + } + + /** + * Remove a bean from the context, calling the destruction callback if any. + * + * @param name bean name + * @return previous value + */ + public Object remove(String name) { + Bean bean = beans.get(name); + if (null != bean) { + beans.remove(name); + bean.destructionCallback.run(); + return bean.object; + } + return null; + } + + /** + * Register the given callback as to be executed after request completion. + * + * @param name The name of the bean. + * @param callback The callback of the bean to be executed for destruction. + */ + public void registerDestructionCallback(String name, Runnable callback) { + Bean bean = beans.computeIfAbsent(name,k->new Bean()); + bean.destructionCallback = callback; + } + + /** Clear all beans and call the destruction callback. */ + public void clear() { + for (Bean bean : beans.values()) { + if (null != bean.destructionCallback) { + bean.destructionCallback.run(); + } + } + beans.clear(); + } + + /** Private class storing bean name and destructor callback. */ + private class Bean { + + private Object object; + private Runnable destructionCallback; + + public Object getObject() { + return object; + } + + public void setObject(Object object) { + this.object = object; + } + + public Runnable getDestructionCallback() { + return destructionCallback; + } + + public void setDestructionCallback(Runnable destructionCallback) { + this.destructionCallback = destructionCallback; + } + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeContextHolder.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeContextHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..54c43f6f7dc945659873378f70338c5f97d097b3 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadScopeContextHolder.java @@ -0,0 +1,47 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus.thread; + +public final class ThreadScopeContextHolder { + + private static final ThreadLocal<ThreadScopeContext> CONTEXT_HOLDER = ThreadLocal + .withInitial(ThreadScopeContext::new); + + private ThreadScopeContextHolder() { + // utility object, not allowed to create instances + } + + /** + * Get the thread specific context. + * + * @return thread scoped context + */ + public static ThreadScopeContext getContext() { + return CONTEXT_HOLDER.get(); + } + + /** + * Set the thread specific context. + * + * @param context thread scoped context + */ + public static void setContext(ThreadScopeContext context) { + CONTEXT_HOLDER.set(context); + } + + public static void clearContext() { + CONTEXT_HOLDER.remove(); + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadSignatureService.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadSignatureService.java new file mode 100644 index 0000000000000000000000000000000000000000..efadcda499e75ef2479aabcfa2d1034dfa1b7ad0 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/messageBus/thread/ThreadSignatureService.java @@ -0,0 +1,142 @@ +package org.opengroup.osdu.notification.provider.azure.messageBus.thread; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import org.apache.commons.lang3.StringUtils; +import org.opengroup.osdu.core.common.cryptographic.HmacData; +import org.opengroup.osdu.core.common.cryptographic.ISignatureService; +import org.opengroup.osdu.core.common.cryptographic.SignatureServiceException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Base64; + +@Component +@Scope(value = "ThreadScope", proxyMode = ScopedProxyMode.TARGET_CLASS) +@ConditionalOnExpression("${azure.serviceBus.enabled:true} || ${azure.eventGridToServiceBus.enabled:true}") +@Primary +public class ThreadSignatureService implements ISignatureService { + private static final String HMAC_SHA_256 = "HmacSHA256"; + private static final String DATA_FORMAT = "{\"expireMillisecond\": \"%s\",\"hashMechanism\": \"hmacSHA256\",\"endpointUrl\": \"%s\",\"nonce\": \"%s\"}"; + private static final String NOTIFICATION_SERVICE = "de-notification-service"; + private static final long EXPIRE_DURATION = 30000L; + private static final String INVALID_SIGNATURE = "Invalid signature"; + private static final String ERROR_GENERATING_SIGNATURE = "Error generating the signature"; + private static final String SIGNATURE_EXPIRED = "Signature is expired"; + private static final String MISSING_HMAC_SIGNATURE = "HMAC signature should not be null or empty"; + private static final String MISSING_SECRET_VALUE = "Secret should not be null or empty"; + private static final String MISSING_ATTRIBUTES_IN_SIGNATURE = "Missing url or nonce or expire time in the signature"; + + public ThreadSignatureService() { + } + + public String getSignedSignature(String url, String secret) throws SignatureServiceException { + if (!Strings.isNullOrEmpty(url) && !Strings.isNullOrEmpty(secret)) { + long currentTime = System.currentTimeMillis(); + String expireTime = String.valueOf(currentTime + 30000L); + String timeStamp = String.valueOf(currentTime); + + try { + String nonce = DatatypeConverter.printHexBinary(this.generateRandomBytes(16)).toLowerCase(); + String data = String.format("{\"expireMillisecond\": \"%s\",\"hashMechanism\": \"hmacSHA256\",\"endpointUrl\": \"%s\",\"nonce\": \"%s\"}", expireTime, url, nonce); + byte[] signature = this.getSignature(secret, nonce, timeStamp, data); + byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8); + String dataBytesEncoded = Base64.getEncoder().encodeToString(dataBytes); + StringBuilder output = new StringBuilder(); + output.append(dataBytesEncoded).append(".").append(DatatypeConverter.printHexBinary(signature).toLowerCase()); + return output.toString(); + } catch (Exception var13) { + throw new SignatureServiceException("Error generating the signature", var13); + } + } else { + throw new SignatureServiceException("Error generating the signature"); + } + } + + public String getSignedSignature(String url, String secret, String expireTime, String nonce) throws SignatureServiceException { + if (!Strings.isNullOrEmpty(url) && !Strings.isNullOrEmpty(secret) && StringUtils.isNumeric(expireTime)) { + long expiry = Long.parseLong(expireTime); + if (System.currentTimeMillis() > expiry) { + throw new SignatureServiceException("Signature is expired"); + } else { + String timeStamp = String.valueOf(expiry - 30000L); + String data = String.format("{\"expireMillisecond\": \"%s\",\"hashMechanism\": \"hmacSHA256\",\"endpointUrl\": \"%s\",\"nonce\": \"%s\"}", expireTime, url, nonce); + + try { + byte[] signature = this.getSignature(secret, nonce, timeStamp, data); + return DatatypeConverter.printHexBinary(signature).toLowerCase(); + } catch (Exception var10) { + throw new SignatureServiceException("Error generating the signature", var10); + } + } + } else { + throw new SignatureServiceException("Error generating the signature"); + } + } + + public void verifyHmacSignature(String hmac, String secret) throws SignatureServiceException { + if (Strings.isNullOrEmpty(hmac)) { + throw new SignatureServiceException("HMAC signature should not be null or empty"); + } else if (Strings.isNullOrEmpty(secret)) { + throw new SignatureServiceException("Secret should not be null or empty"); + } else { + String[] tokens = hmac.split("\\."); + if (tokens.length != 2) { + throw new SignatureServiceException("Invalid signature"); + } else { + byte[] dataBytes = Base64.getDecoder().decode(tokens[0]); + String requestSignature = tokens[1]; + String data = new String(dataBytes, StandardCharsets.UTF_8); + HmacData hmacData = (HmacData)(new Gson()).fromJson(data, HmacData.class); + String url = hmacData.getEndpointUrl(); + String nonce = hmacData.getNonce(); + String expireTime = hmacData.getExpireMillisecond(); + if (!Strings.isNullOrEmpty(url) && !Strings.isNullOrEmpty(nonce) && !Strings.isNullOrEmpty(expireTime)) { + String newSignature = this.getSignedSignature(url, secret, expireTime, nonce); + if (!requestSignature.equalsIgnoreCase(newSignature)) { + throw new SignatureServiceException("Invalid signature"); + } + } else { + throw new SignatureServiceException("Missing url or nonce or expire time in the signature"); + } + } + } + } + + private byte[] getSignature(String secret, String nonce, String timeStamp, String data) throws Exception { + byte[] secretBytes = DatatypeConverter.parseHexBinary(secret); + byte[] nonceBytes = DatatypeConverter.parseHexBinary(nonce); + byte[] encryptedNonce = this.computeHmacSha256(nonceBytes, secretBytes); + byte[] encryptedTimestamp = this.computeHmacSha256(timeStamp, encryptedNonce); + byte[] signedKey = this.computeHmacSha256("de-notification-service", encryptedTimestamp); + byte[] signature = this.computeHmacSha256(data, signedKey); + return signature; + } + + private byte[] computeHmacSha256(String data, byte[] key) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + return mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); + } + + private byte[] computeHmacSha256(byte[] data, byte[] key) throws Exception { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + return mac.doFinal(data); + } + + private byte[] generateRandomBytes(int size) { + byte[] key = new byte[size]; + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(key); + return key; + } +} \ No newline at end of file diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationContent.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationContent.java new file mode 100644 index 0000000000000000000000000000000000000000..d3edc232272b90b1e5ab30dcb89f672eacd49dab --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationContent.java @@ -0,0 +1,40 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.models; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +@Data +@AllArgsConstructor +@Builder +public class NotificationContent { + String NotificationId; + String data; + Map<String, String> extractAttributes; + boolean isHandShakeRequest; + + public static NotificationContent mapFrom(String notificationId, String pubSubData, Map<String, String> attributes, boolean isHandShake) { + NotificationContentBuilder notificationContentBuilder = NotificationContent.builder() + .NotificationId(notificationId) + .data(pubSubData) + .extractAttributes(attributes) + .isHandShakeRequest(isHandShake); + return notificationContentBuilder.build(); + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationRequest.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationEventGridRequest.java similarity index 95% rename from provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationRequest.java rename to provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationEventGridRequest.java index 32236c6e234268a2e0a92d05aa423661819387ff..1e3445d1526f79ea674c4715e9bd7231256d3aea 100644 --- a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationRequest.java +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationEventGridRequest.java @@ -23,7 +23,7 @@ import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PRIVATE) -public class NotificationRequest { +public class NotificationEventGridRequest { private String id; diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationEventGridServiceBusRequest.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationEventGridServiceBusRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..da13b71ccb3cf94e804198eaff6b87fc70ab57d8 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationEventGridServiceBusRequest.java @@ -0,0 +1,42 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.models; + +import com.google.gson.JsonObject; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class NotificationEventGridServiceBusRequest { + private String id; + + private String eventType; + + private String subject; + + private JsonObject data; + + private String dataVersion; + + private String eventTime; + + private String metadataVersion; + + private String topic; +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationServiceBusRequest.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationServiceBusRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..02f559d11595915c0c4bb82e8c2d3040094b7f57 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/models/NotificationServiceBusRequest.java @@ -0,0 +1,28 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.models; + +import com.google.gson.JsonObject; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class NotificationServiceBusRequest { + private JsonObject message; +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/pubsub/EventGridRequestBodyExtractor.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/pubsub/EventGridRequestBodyExtractor.java index b3c4aeb6b55e27936af4e0be2d42d66b4e00cbd0..fb5ba29ef3892508a195680d253c989eea209219 100644 --- a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/pubsub/EventGridRequestBodyExtractor.java +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/pubsub/EventGridRequestBodyExtractor.java @@ -22,14 +22,14 @@ import com.google.gson.JsonObject; import lombok.SneakyThrows; import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.notification.provider.azure.models.NotificationContent; import org.opengroup.osdu.notification.provider.azure.models.HandshakeRequestData; import org.opengroup.osdu.notification.provider.azure.models.NotificationRecordsChangedData; -import org.opengroup.osdu.notification.provider.azure.models.NotificationRequest; +import org.opengroup.osdu.notification.provider.azure.models.NotificationEventGridRequest; import org.opengroup.osdu.notification.provider.interfaces.IPubsubRequestBodyExtractor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.context.annotation.RequestScope; import javax.servlet.http.HttpServletRequest; @@ -56,10 +56,11 @@ public class EventGridRequestBodyExtractor implements IPubsubRequestBodyExtracto private final HttpServletRequest httpServletRequest; private final JaxRsDpsLog logger; - private final NotificationRequest notificationRequest; + private final NotificationEventGridRequest notificationRequest; private NotificationRecordsChangedData notificationRecordsChangedData; private HandshakeRequestData handshakeRequestData; private boolean isHandshakeRequest; + private NotificationContent notificationContent; @Autowired public EventGridRequestBodyExtractor(HttpServletRequest httpServletRequest, JaxRsDpsLog log) { @@ -151,12 +152,12 @@ public class EventGridRequestBodyExtractor implements IPubsubRequestBodyExtracto */ @SneakyThrows // TODO : @komakkar sanitize the exceptions to match the SpringExceptionMapper and throw ValidationException - private NotificationRequest extractNotificationRequestFromHttpRequest() { - NotificationRequest notificationRequest = null; + private NotificationEventGridRequest extractNotificationRequestFromHttpRequest() { + NotificationEventGridRequest notificationRequest = null; if (this.notificationRequest == null && this.httpServletRequest.getMethod().equalsIgnoreCase("post")) { try { String requestBody = getBody(this.httpServletRequest); - NotificationRequest[] notificationRequestArray = GSON.fromJson(requestBody, NotificationRequest[].class); + NotificationEventGridRequest[] notificationRequestArray = GSON.fromJson(requestBody, NotificationEventGridRequest[].class); notificationRequest = notificationRequestArray[0]; this.isHandshakeRequest = notificationRequest.getEventType().equals(EVENTGRID_VALIDATION_EVENT); @@ -173,12 +174,12 @@ public class EventGridRequestBodyExtractor implements IPubsubRequestBodyExtracto return notificationRequest; } - private void extractHandshakeData(NotificationRequest notificationRequest) { + private void extractHandshakeData(NotificationEventGridRequest notificationRequest) { this.handshakeRequestData = GSON.fromJson(notificationRequest.getData(), HandshakeRequestData.class); Preconditions.checkNotNull(this.handshakeRequestData.getValidationCode(), "Request payload parsing error handshkae"); } - private void extractNotificationData(NotificationRequest notificationRequest) { + private void extractNotificationData(NotificationEventGridRequest notificationRequest) { NotificationRecordsChangedData notificationRecordsChangedData = GSON.fromJson(notificationRequest.getData(), NotificationRecordsChangedData.class); verifyNotificationData(notificationRecordsChangedData); this.notificationRecordsChangedData = notificationRecordsChangedData; diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureCosmosProperties.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureCosmosProperties.java index c606785dabc1d8730bc3426d76dc8c73dab4471a..7a9fef37308bba5e0b088ea09537d7d45280672e 100644 --- a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureCosmosProperties.java +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureCosmosProperties.java @@ -12,6 +12,9 @@ public class AzureCosmosProperties { @Value("${tenantinfo.container.name}") private String tenantInfoContainerName; + @Value("${registerSubscription.container.name}") + private String registerSubscriptionContainerName; + @Value("${azure.cosmosdb.database}") private String cosmosDBName; @@ -21,6 +24,12 @@ public class AzureCosmosProperties { return tenantInfoContainerName; } + @Bean + @Named("COSMOS_CONTAINER_NAME") + public String registerSubscriptionContainerName() { + return registerSubscriptionContainerName; + } + @Bean @Named("COSMOS_DB_NAME") public String cosmosDBName() { diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureServiceAccountValidatorImpl.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureServiceAccountValidatorImpl.java index 7aaccda189813531f38703cca25ee74252cc3570..7fd42f301fd5022a2c058a068b3cf5dee73fceb6 100644 --- a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureServiceAccountValidatorImpl.java +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureServiceAccountValidatorImpl.java @@ -44,7 +44,7 @@ public class AzureServiceAccountValidatorImpl implements IServiceAccountValidato String appIdClaim = jwt.getClaim(APP_ID_CLAIM).asString(); if(appIdClaim!= null && appIdClaim.equals(userIdentity)) { - logger.info("PubSub authorized"); + logger.debug("PubSub authorized"); return true; } return false; diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureServiceBusConfig.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureServiceBusConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..b9df67e2e4f6aec63c1c7c8cbbd818fff2653d05 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/AzureServiceBusConfig.java @@ -0,0 +1,53 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.util; + +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Getter +public class AzureServiceBusConfig { + @Value("${executor-n-threads}") + private String nThreads; + + @Value("${max-concurrent-calls}") + private String maxConcurrentCalls; + + @Value("${max-lock-renew}") + private String maxLockRenewDurationInSeconds; + + @Value("${initial-subscription-manager-delay}") + private String initialSubscriptionManagerDelay; + + @Value("${consecutive-subscription-manager-delay}") + private String consecutiveSubscriptionManagerDelay; + + @Value("${service-bus-enabled}") + private String ServiceBusEnabled; + + @Value("${event-grid-to-service-bus-enabled}") + private String EventGridToServiceBusEnabled; + + @Bean + public MDCContextMap mdcContextMap() { + return new MDCContextMap(); + } + +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/MDCContextMap.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/MDCContextMap.java new file mode 100644 index 0000000000000000000000000000000000000000..57c08cb18613784974dc7260041b3a44e693d6d2 --- /dev/null +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/MDCContextMap.java @@ -0,0 +1,30 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.util; + +import org.opengroup.osdu.core.common.model.http.DpsHeaders; + +import java.util.HashMap; +import java.util.Map; + +public class MDCContextMap { + + public Map<String, String> getContextMap(String correlationId, String dataPartitionId) { + final Map<String, String> contextMap = new HashMap<>(); + contextMap.put(DpsHeaders.CORRELATION_ID, correlationId); + contextMap.put(DpsHeaders.DATA_PARTITION_ID, dataPartitionId); + return contextMap; + } +} diff --git a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/ServiceAccountJwtAzureClientImpl.java b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/ServiceAccountJwtAzureClientImpl.java index 934dd8672caad9ed2ad945c1af2761d640d1b7b9..88d68e6cfc9dbc3070782945b9309e5a92650784 100644 --- a/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/ServiceAccountJwtAzureClientImpl.java +++ b/provider/notification-azure/src/main/java/org/opengroup/osdu/notification/provider/azure/util/ServiceAccountJwtAzureClientImpl.java @@ -14,82 +14,19 @@ package org.opengroup.osdu.notification.provider.azure.util; -import com.auth0.jwt.JWT; -import com.microsoft.aad.adal4j.AuthenticationContext; -import com.microsoft.aad.adal4j.AuthenticationResult; -import com.microsoft.aad.adal4j.ClientCredential; -import org.apache.http.HttpStatus; -import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.search.IdToken; -import org.opengroup.osdu.core.common.provider.interfaces.IJwtCache; +import org.opengroup.osdu.azure.util.AzureServicePrincipleTokenService; import org.opengroup.osdu.core.common.util.IServiceAccountJwtClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.net.MalformedURLException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - @Component public class ServiceAccountJwtAzureClientImpl implements IServiceAccountJwtClient { @Autowired - private AppProperties config; - - @Autowired - private IJwtCache tenantJwtCache; - - public String getIdToken(String tenantName) { - String ACCESS_TOKEN = ""; - ExecutorService service = null; - - try { - // TODO : Refactor to move ID token form Common.Core.model.search to Common.core - IdToken cachedToken = (IdToken) this.tenantJwtCache.get(tenantName); - - if ((cachedToken != null) && !IdToken.refreshToken(cachedToken)) { - return "Bearer " + cachedToken.getTokenValue(); - } - - // TODO : Control the thread count via config and pool should be created once. - service = Executors.newFixedThreadPool(1); - - ACCESS_TOKEN = getAccessToken(service); - IdToken idToken = IdToken.builder().tokenValue(ACCESS_TOKEN).expirationTimeMillis(JWT.decode(ACCESS_TOKEN).getExpiresAt().getTime()).build(); - this.tenantJwtCache.put(tenantName, idToken); - } finally { - if(service != null) { - service.shutdown(); - } - } - return "Bearer " + ACCESS_TOKEN; - } - - // TODO : Refactor for making it test-able. - // THIS METHOD IS PUBLIC ONLY TO ENABLE UNIT TESTING - public String getAccessToken(ExecutorService service) { - AuthenticationContext context = null; - ClientCredential credential = null; - String ACCESS_TOKEN = null; - try { - context = new AuthenticationContext(this.config.getAuthURL(), false, service); - credential = new ClientCredential(this.config.getAuthClientID(), this.config.getAuthClientSecret()); - - Future<AuthenticationResult> future = context.acquireToken(this.config.getAadClientID(), credential, null); + private AzureServicePrincipleTokenService tokenService; - if (future == null) { - throw new AppException(HttpStatus.SC_FORBIDDEN, "Token not generated", "The user is not authorized to obtain Token From AAD"); - } - ACCESS_TOKEN = future.get().getAccessToken(); - } catch (MalformedURLException malformedURLException) { - malformedURLException.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (ExecutionException e) { - e.printStackTrace(); - } - return ACCESS_TOKEN; + @Override + public String getIdToken(String partitionId){ + return "Bearer " + this.tokenService.getAuthorizationToken(); } } diff --git a/provider/notification-azure/src/main/resources/application.properties b/provider/notification-azure/src/main/resources/application.properties index 3864e3180e5b7a998f7fc6ce072003b0e7e71b60..8855e8376cd707cc0efbc4413111f0119f03cc50 100644 --- a/provider/notification-azure/src/main/resources/application.properties +++ b/provider/notification-azure/src/main/resources/application.properties @@ -1,4 +1,4 @@ -# Copyright © Microsoft Corporation +# Copyright © Microsoft Corporation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,10 +20,15 @@ server.error.whitelabel.enabled=false # Logging configuration LOG_PREFIX=${LOG_PREFIX} -logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.web=${notification_spring_logging_level:INFO} logging.transaction.enabled=true logging.slf4jlogger.enabled=true logging.mdccontext.enabled=false +logging.ignore.servlet.paths=/swagger-ui.html + +# Need below two properties for Entitlements config bean initialization due to a primary bean added in core-lib-azure which takes precedence over EntitlementsClientFactory class in notification service +AUTHORIZE_API=${entitlements_service_endpoint} +AUTHORIZE_API_KEY=${entitlements_service_api_key} # Service settings PARTITION_API=${partition_service_endpoint} @@ -51,4 +56,31 @@ azure.keyvault.url=${KEYVAULT_URI} #TenantFactory Configuration tenantFactoryImpl.required=true -tenantInfo.container.name=TenantInfo \ No newline at end of file +tenantInfo.container.name=TenantInfo + +#RegisterSubscription Configuration +registerSubscription.container.name=RegisterSubscription + +# Specifies the number of threads to be created on the thread pool +executor-n-threads=${executor_n_threads} + +# Specifies the maximum number of concurrent calls to the callback the message pump should initiate +max-concurrent-calls=${max_concurrent_calls} + +# Specifies the maximum duration in seconds within which the lock will be renewed automatically +max-lock-renew=${max_lock_renew_duration_seconds} + +# Specifies the initial delay before calling subscribeNotificationsEvent +initial-subscription-manager-delay=${initial_subscription_manager_delay_seconds} + +# Specifies the consecutive thread delay for subscribeNotificationsEvent +consecutive-subscription-manager-delay=${consecutive_subscription_manager_delay_seconds} + +# Specifies if Service Bus is enabled +azure.serviceBus.enabled=${service_bus_enabled} + +# Specifies if Event Grid To Service Bus is enabled +azure.eventGridToServiceBus.enabled=${event_grid_to_service_bus_enabled} + +# Specifies if Event Grid is enabled. Used to override the non-request scoped beans +requestScope.enabled=${event_grid_enabled} diff --git a/provider/notification-azure/src/main/resources/migrationConfig.json b/provider/notification-azure/src/main/resources/migrationConfig.json new file mode 100644 index 0000000000000000000000000000000000000000..f7b6f574d5fb08592b31d53c1bb6663c9447f78a --- /dev/null +++ b/provider/notification-azure/src/main/resources/migrationConfig.json @@ -0,0 +1,18 @@ +[ + { + "ResourceGroupIdCosmos": "This is the subscription ID corresponding to a resource group having Cosmos DB", + "PartitionKeyCosmos": "This is used by Cosmos DB to distribute data among multiple partitions.Example -opendes", + "ReadWriteMasterKeyCosmosDb": "Cosmos read write keys are very sensitive ones and provide access to the administrative resources", + "CosmosDBEndPoint": "Cosmos endpoint URL", + "DatabaseName": "Cosmos Database name. Example - osdu-db", + "ContainerName": "Cosmos Container name. Example -RegisterSubscription", + "ResourceGroupNameServiceBus": "This is the resource group name of a resource group having Service bus", + "ResourceGroupIdServiceBus": "This is the subscription ID corresponding to a resource group having Service Bus.This can be same as ResourceGroupIdCosmos mentioned above", + "NamespaceNameServiceBus": "Service bus namespace name(not host name) under ResourceGroupNameServiceBus", + "ServiceBusTopicName": "Service bus topic name under ResourceGroupNameServiceBus", + "SubscriptionIdsCosmos": [ + "Subs_Id1", + "Subs_Id2" + ] + } +] \ No newline at end of file diff --git a/provider/notification-azure/src/main/resources/migrationSetup.ps1 b/provider/notification-azure/src/main/resources/migrationSetup.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..a9e08258953d15af730618487d06dd7af79bb8e1 --- /dev/null +++ b/provider/notification-azure/src/main/resources/migrationSetup.ps1 @@ -0,0 +1,24 @@ +# This script provides the pre migration setup for subscriptions from event grid to service bus. + +param([Parameter(Mandatory = $true)][Boolean]$InstallAzModule) + +try +{ + # Set execution policy + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + + # Installation of modules. This Can be ignored by $InstallAzModule if already installed. + if ($InstallAzModule) + { + Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force -ErrorAction Stop + } + + # Azure login + Connect-AzAccount -ErrorAction Stop + + Write-Output "Migration Setup Successful." +} +catch +{ + Write-Output "Migration Setup Failed" $_ +} \ No newline at end of file diff --git a/provider/notification-azure/src/main/resources/migrationToServiceBus.ps1 b/provider/notification-azure/src/main/resources/migrationToServiceBus.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..e42d68148d1d8df2be0065c6c1a087413f854faa --- /dev/null +++ b/provider/notification-azure/src/main/resources/migrationToServiceBus.ps1 @@ -0,0 +1,162 @@ +<# This script performs migration of event grid subscriptions to service bus subscriptions for a given configuration file. + migrationSetup.ps1 is the prerequistes for successful execution of this script. +#> +Param([Parameter(Mandatory = $true)][String]$migrationConfigFilePath) + +Add-Type -AssemblyName System.Web + +# Generates auth key for cosmos API +Function GenerateAuthorizationSignature +{ + [CmdletBinding()] + Param + ( + [Parameter(Mandatory = $true)][String]$method, + [Parameter(Mandatory = $true)][String]$resourceLink, + [Parameter(Mandatory = $true)][String]$resourceType, + [Parameter(Mandatory = $true)][String]$key, + [Parameter(Mandatory = $true)][String]$keyType, + [Parameter(Mandatory = $true)][String]$tokenVersion, + [Parameter(Mandatory = $true)][String]$dateTime + ) + $hmacSha = New-Object System.Security.Cryptography.HMACSHA256 + $hmacSha.Key = [System.Convert]::FromBase64String($key) + + $payLoad = "$($method.ToLowerInvariant() )`n$($resourceType.ToLowerInvariant() )`n$resourceLink`n$($dateTime.ToLowerInvariant() )`n`n" + $hashPayLoad = $hmacSha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($payLoad)) + $signature = [System.Convert]::ToBase64String($hashPayLoad); + + [System.Web.HttpUtility]::UrlEncode("type=$keyType&ver=$tokenVersion&sig=$signature") +} + +# Fetch the subscription from cosmos db +Function GetCosmosSubscription +{ + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)][String]$Partition, + [Parameter(Mandatory = $true)][String]$SubscriptionId, + [Parameter(Mandatory = $true)][String]$ReadWriteMasterKeyCosmosDb, + [Parameter(Mandatory = $true)][String]$CosmosDBEndPoint, + [Parameter(Mandatory = $true)][String]$DatabaseName, + [Parameter(Mandatory = $true)][String]$ContainerName + ) + + $resourceType = "docs"; + $resourceLink = "dbs/$DatabaseName/colls/$ContainerName/docs/$SubscriptionId" + $partitionkey = "[""$( $Partition )""]" + $cosmosURI = "$CosmosDBEndPoint$resourceLink" + $dateTime = [DateTime]::UtcNow.ToString("r") + + $authToken = GenerateAuthorizationSignature -method "GET" -resourceLink $resourceLink -resourceType $resourceType -key $ReadWriteMasterKeyCosmosDb -keyType "master" -tokenVersion "1.0" -dateTime $dateTime + + $headers = @{ authorization = $authToken; "x-ms-version" = "2017-02-22"; "x-ms-documentdb-partitionkey" = $partitionkey; "x-ms-date" = $dateTime } + + # Call cosmos API to get subscription + Invoke-RestMethod -Method "GET" -Uri $cosmosURI -headers $headers -Verbose + +} + +# Update the cosmos subscription +Function UpdateCosmosSubscription +{ + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true)][String]$JsonSubscription, + [Parameter(Mandatory = $true)][String]$Partition, + [Parameter(Mandatory = $true)][String]$SubscriptionId, + [Parameter(Mandatory = $true)][String]$ReadWriteMasterKeyCosmosDb, + [Parameter(Mandatory = $true)][String]$CosmosDBEndPoint, + [Parameter(Mandatory = $true)][String]$DatabaseName, + [Parameter(Mandatory = $true)][String]$ContainerName + ) + + $resourceType = "docs"; + $resourceLink = "dbs/$DatabaseName/colls/$ContainerName/docs/$SubscriptionId" + $partitionkey = "[""$( $Partition )""]" + $contentType = "application/json" + $cosmosURI = "$CosmosDBEndPoint$resourceLink" + $dateTime = [DateTime]::UtcNow.ToString("r") + + $authToken = GenerateAuthorizationSignature -method "PUT" -resourceLink $ResourceLink -resourceType $ResourceType -key $ReadWriteMasterKeyCosmosDb -keyType "master" -tokenVersion "1.0" -dateTime $dateTime + + $headers = @{ authorization = $authToken; "x-ms-version" = "2017-02-22"; "x-ms-documentdb-partitionkey" = $partitionkey; "x-ms-date" = $dateTime } + + # Call cosmos API to update subscription + Invoke-RestMethod -Method "PUT" -ContentType $contentType -Uri $cosmosURI -headers $headers -Body $JsonSubscription -Verbose + +} + +# Migration Execution +try +{ + $configurations = Get-Content -Raw -Path $migrationConfigFilePath| ConvertFrom-Json + + foreach ($config in $configurations) + { + try + { + $ResourceGroupIdCosmos = $config.ResourceGroupIdCosmos + $PartitionKeyCosmos = $config.PartitionKeyCosmos + $ReadWriteMasterKeyCosmosDb = $config.ReadWriteMasterKeyCosmosDb + $CosmosDBEndPoint = $config.CosmosDBEndPoint + $DatabaseName = $config.DatabaseName + $ContainerName = $config.ContainerName + $ResourceGroupNameServiceBus = $config.ResourceGroupNameServiceBus + $ResourceGroupIdServiceBus = $config.ResourceGroupIdServiceBus + $NamespaceNameServiceBus = $config.NamespaceNameServiceBus + $ServiceBusTopicName = $config.ServiceBusTopicName + $SubscriptionIds = $config.SubscriptionIdsCosmos + + foreach ($SubscriptionId in $SubscriptionIds) + { + $Subscription = $null + $ServiceBusSubscription = $null + try + { + # Set resource group susbscription for cosmos + Select-AzSubscription -SubscriptionName $ResourceGroupIdCosmos -Verbose -ErrorAction Stop + + $Subscription = GetCosmosSubscription -Partition $PartitionKeyCosmos -SubscriptionId $SubscriptionId -ReadWriteMasterKeyCosmosDb $ReadWriteMasterKeyCosmosDb -CosmosDBEndPoint $CosmosDBEndPoint -DatabaseName $DatabaseName -ContainerName $ContainerName + if ((!$Subscription) -or (!$Subscription.notificationId) -or (!$Subscription.topic)) + { + throw "Subscription is not present/null/empty or NotificationId/Topic is not present/null/empty in subscription with ID - $SubscriptionId" + } + $NotificationId = $Subscription.notificationId + + # Set resource group susbscription for service bus + Select-AzSubscription -SubscriptionName $ResourceGroupIdServiceBus -Verbose -ErrorAction Stop + + # Create service bus topic subscription + $ServiceBusSubscription = New-AzServiceBusSubscription -ResourceGroupName $ResourceGroupNameServiceBus -Namespace $NamespaceNameServiceBus -Topic $ServiceBusTopicName -Name $NotificationId -Verbose + + if (!$ServiceBusSubscription) + { + throw "Unable to create service bus subscription with Id - $NotificationId and topic - $ServiceBusTopicName " + } + # Update subscription topic name + $Subscription.topic = $ServiceBusTopicName + $JsonSubscription = $Subscription| ConvertTo-Json + + # Update cosmos subscription + $UpdatedSubscription = UpdateCosmosSubscription -JsonSubscription $JsonSubscription -Partition $PartitionKeyCosmos -SubscriptionId $SubscriptionId -ReadWriteMasterKeyCosmosDb $ReadWriteMasterKeyCosmosDb -CosmosDBEndPoint $CosmosDBEndPoint -DatabaseName $DatabaseName -ContainerName $ContainerName + + Write-Output "Migration to Service Bus is Successful for Subscription - $SubscriptionId `n" + } + catch + { + Write-Output "Migration Failed for Subscription - $SubscriptionId." $_ + } + } + } + catch + { + Write-Output "Unable to process configuration -$config.ResourceGroupName." $_ + } + } + +} +catch +{ + Write-Output "Unable to process configurations" $_ +} \ No newline at end of file diff --git a/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/MessageHandlerTest.java b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/MessageHandlerTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c5ee287a6ff5158a0cc704a5f22ea5156e50c184 --- /dev/null +++ b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/MessageHandlerTest.java @@ -0,0 +1,71 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.messageBus; + +import com.microsoft.azure.servicebus.Message; +import com.microsoft.azure.servicebus.SubscriptionClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opengroup.osdu.notification.provider.azure.messageBus.MessageHandler; +import org.opengroup.osdu.notification.provider.azure.messageBus.ProcessNotification; + +import java.util.UUID; + +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class MessageHandlerTest { + + private static final UUID uuid = UUID.randomUUID(); + private static final String subscriptionName = "TestSubscription"; + + @InjectMocks + private MessageHandler messageHandler; + + @Mock + private ProcessNotification processNotification; + + @Mock + private SubscriptionClient subscriptionClient; + + @Mock + private Message message; + + @BeforeEach + public void init() { + when(message.getLockToken()).thenReturn(uuid); + when(subscriptionClient.getSubscriptionName()).thenReturn(subscriptionName); + } + + @Test + public void shouldInvokeCompleteAsync() throws Exception { + lenient().doNothing().when(processNotification).performNotification(message, subscriptionName); + messageHandler.onMessageAsync(message); + verify(subscriptionClient, times(1)).completeAsync(uuid); + verify(processNotification, times(1)).performNotification(message, subscriptionClient.getSubscriptionName()); + } + + @Test + public void shouldInvokeAbandonAsyncWhenProcessNotificationThrowsException() throws Exception { + doThrow(new Exception()).when(processNotification).performNotification(message, subscriptionName); + messageHandler.onMessageAsync(message); + verify(subscriptionClient, times(1)).abandonAsync(uuid); + verify(processNotification, times(1)).performNotification(message, subscriptionClient.getSubscriptionName()); + } +} diff --git a/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/ProcessNotificationTest.java b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/ProcessNotificationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..927672d6d6d69985f3c459cb9557467d81a579c2 --- /dev/null +++ b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/ProcessNotificationTest.java @@ -0,0 +1,119 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.messageBus; + +import com.microsoft.azure.servicebus.Message; +import org.junit.Assert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opengroup.osdu.core.common.http.HttpResponse; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.notification.provider.azure.messageBus.ProcessNotification; +import org.opengroup.osdu.notification.provider.azure.messageBus.extractor.RequestBodyAdapter; +import org.opengroup.osdu.notification.provider.azure.messageBus.thread.ThreadDpsHeaders; +import org.opengroup.osdu.notification.provider.azure.models.NotificationContent; +import org.opengroup.osdu.notification.provider.azure.util.MDCContextMap; +import org.opengroup.osdu.notification.service.NotificationHandler; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class ProcessNotificationTest { + private static final String dataPartitionId = "opendes"; + private static final String correlationId = "908fcf8d-30c5-4c74-a0ae-ab47b48b7a85"; + private static final String notificationData = "[{\"id\":\"opendes:doc:\",\"kind\":\"opendes:at:wellbore:1.0.0\",\"op\":\"create\"},{\"id\":\"opendes:doc:\",\"kind\":\"opendes:at:wellbore:1.0.0\",\"op\":\"create\"}]"; + private static final String subscriptionName = "TestSubscription"; + private static final String notificationId = "Notification-Test-Subscription"; + private HttpResponse response = new HttpResponse(); + private static final Map<String, String> requestAttributes = new HashMap(); + private final String NOT_ACKNOWLEDGE = "message not acknowledged by client"; + private final String EXCEPTION_NOT_THROWN = "Should Throw Exception"; + @InjectMocks + private ProcessNotification processNotification; + + @Mock + private NotificationHandler notificationHandler; + @Mock + private RequestBodyAdapter requestBodyAdapter; + @Mock + private Message message; + @Mock + private NotificationContent notificationContent; + @Mock + private MDCContextMap mdcContextMap; + @Spy + private ThreadDpsHeaders dpsHeaders; + + @BeforeEach + public void init() { + requestAttributes.put(DpsHeaders.DATA_PARTITION_ID, dataPartitionId); + requestAttributes.put(DpsHeaders.CORRELATION_ID, correlationId); + lenient().doNothing().when(dpsHeaders).setThreadContext(dataPartitionId, correlationId); + lenient().when(mdcContextMap.getContextMap(dataPartitionId, correlationId)).thenReturn(new HashMap<>()); + lenient().when(dpsHeaders.getHeaders()).thenReturn(requestAttributes); + when(notificationContent.getExtractAttributes()).thenReturn(requestAttributes); + when(notificationContent.getNotificationId()).thenReturn(notificationId); + when(notificationContent.getData()).thenReturn(notificationData); + } + + @Test + public void shouldSuccessfullyPerformNotification() throws Exception { + response.setResponseCode(200); + when(requestBodyAdapter.extractNotificationContent(message, subscriptionName)).thenReturn(notificationContent); + when(notificationHandler.notifySubscriber(notificationId, notificationData, requestAttributes)).thenReturn(response); + processNotification.performNotification(message, subscriptionName); + verify(notificationHandler, times(1)).notifySubscriber(notificationId, notificationData, requestAttributes); + verify(requestBodyAdapter, times(1)).extractNotificationContent(message, subscriptionName); + } + + @Test + public void shouldThrowExceptionWhenNotifySubscriberFails() throws Exception { + response.setResponseCode(400); + when(requestBodyAdapter.extractNotificationContent(message, subscriptionName)).thenReturn(notificationContent); + when(notificationHandler.notifySubscriber(notificationId, notificationData, requestAttributes)).thenReturn(response); + try { + processNotification.performNotification(message, subscriptionName); + fail(EXCEPTION_NOT_THROWN); + } catch (Exception e) { + verify(notificationHandler, times(1)).notifySubscriber(notificationId, notificationData, requestAttributes); + verify(requestBodyAdapter, times(1)).extractNotificationContent(message, subscriptionName); + Assert.assertEquals(NOT_ACKNOWLEDGE, e.getMessage()); + } + } + + @Test + public void shouldThrowExceptionWhenNotifySubscriberThrowsException() throws Exception { + response.setResponseCode(400); + when(requestBodyAdapter.extractNotificationContent(message, subscriptionName)).thenReturn(notificationContent); + doThrow(new Exception()).when(notificationHandler).notifySubscriber(notificationId, notificationData, requestAttributes); + try { + processNotification.performNotification(message, subscriptionName); + fail(EXCEPTION_NOT_THROWN); + } catch (Exception e) { + verify(notificationHandler, times(1)).notifySubscriber(notificationId, notificationData, requestAttributes); + verify(requestBodyAdapter, times(1)).extractNotificationContent(message, subscriptionName); + Assert.assertNotNull(e); + } + } +} diff --git a/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/SubscriptionClientFactoryTest.java b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/SubscriptionClientFactoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..dee2f7f0e5151cbabf538460eb1281d77e46e7a2 --- /dev/null +++ b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/SubscriptionClientFactoryTest.java @@ -0,0 +1,71 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.messageBus; + +import com.microsoft.azure.servicebus.SubscriptionClient; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opengroup.osdu.azure.servicebus.ISubscriptionClientFactory; +import org.opengroup.osdu.notification.provider.azure.messageBus.SubscriptionClientFactImpl; + +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class SubscriptionClientFactoryTest { + @InjectMocks + private SubscriptionClientFactImpl subsClientFactory; + + @Mock + private SubscriptionClient subscriptionClient; + + @Mock + private ISubscriptionClientFactory subscriptionClientFactory; + + private static final String sbTopic = "testTopic"; + private static final String sbSubscription = "testSubscription"; + private static final String dataPartition = "testPartition"; + private final String EXCEPTION_NOT_THROWN = "Should Throw Exception"; + private final String SERVICE_BUS_EXCEPTION = "Unable to retrieve client info from Service Bus"; + + @Test + public void subscriptionClientShouldNotBeNull() throws ServiceBusException, InterruptedException { + when(subscriptionClientFactory.getClient(dataPartition, sbTopic, sbSubscription)) + .thenReturn(subscriptionClient); + SubscriptionClient result = subsClientFactory.getSubscriptionClient(dataPartition, sbTopic, sbSubscription); + assertNotNull(result); + assertEquals(subscriptionClient, result); + } + + @Test + public void shouldThrowExceptionWhenSubscriptionClientThrowsException() throws ServiceBusException, InterruptedException { + when(subscriptionClientFactory.getClient(dataPartition, sbTopic, sbSubscription)) + .thenThrow(new ServiceBusException(false, SERVICE_BUS_EXCEPTION)); + try { + subsClientFactory.getSubscriptionClient(dataPartition, sbTopic, sbSubscription); + fail(EXCEPTION_NOT_THROWN); + } catch (Exception e) { + Assert.assertNotNull(e); + Assert.assertEquals( SERVICE_BUS_EXCEPTION, e.getMessage()); + } + } +} diff --git a/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/SubscriptionManagerImplTest.java b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/SubscriptionManagerImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c1f6216898b1f660f7d8846322537ac83bcd35fc --- /dev/null +++ b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/messageBus/SubscriptionManagerImplTest.java @@ -0,0 +1,182 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.messageBus; + +import com.microsoft.azure.servicebus.SubscriptionClient; +import com.microsoft.azure.servicebus.primitives.ServiceBusException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.opengroup.osdu.azure.cosmosdb.CosmosStore; +import org.opengroup.osdu.azure.partition.PartitionServiceClient; +import org.opengroup.osdu.core.common.model.notification.HmacSecret; +import org.opengroup.osdu.core.common.model.notification.Subscription; +import org.opengroup.osdu.core.common.model.tenant.TenantInfo; +import org.opengroup.osdu.core.common.notification.ISubscriptionFactory; +import org.opengroup.osdu.core.common.provider.interfaces.ITenantFactory; +import org.opengroup.osdu.notification.provider.azure.messageBus.ProcessNotification; +import org.opengroup.osdu.notification.provider.azure.messageBus.SubscriptionClientFactImpl; +import org.opengroup.osdu.notification.provider.azure.messageBus.SubscriptionManagerImpl; +import org.opengroup.osdu.notification.provider.azure.messageBus.thread.ThreadDpsHeaders; +import org.opengroup.osdu.notification.provider.azure.util.AzureServiceBusConfig; +import org.opengroup.osdu.notification.provider.azure.util.AzureCosmosProperties; + +import java.util.Collections; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static java.lang.Thread.sleep; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class SubscriptionManagerImplTest { + + private static final String maxLockRenewDuration = "60"; + private static final String maxConcurrentCalls = "1"; + private static final String nThreads = "2"; + private static final String errorMessage = "some-error"; + + @InjectMocks + private SubscriptionManagerImpl subscriptionManager; + @Mock + private SubscriptionClientFactImpl subscriptionClientFactory; + @Mock + private SubscriptionClient subscriptionClient; + @Mock + private ProcessNotification processNotification; + @Mock + private ITenantFactory tenantFactory; + @Mock + private ISubscriptionFactory subscriptionFactory; + @Mock + private ThreadDpsHeaders dpsHeaders; + @Mock + private PartitionServiceClient partitionService; + @Mock + private CosmosStore cosmosStore; + @Mock + private AzureCosmosProperties azureCosmosProperties; + @Mock + private AzureServiceBusConfig azureServiceBusConfig; + + private static final String dataPartition = "testTenant"; + private static final String cosmosDbName = "testDatabase"; + private static final String registerContainerName = "testContainer"; + private static final String sbTopic = "testTopic"; + private static final String sbSubscription = "testSubscription"; + private static final String initial_thread_delay = "0"; + private static final String thread_delay = "1"; + + @BeforeEach + public void init() { + TenantInfo tenantInfo = new TenantInfo(); + tenantInfo.setDataPartitionId(dataPartition); + lenient().when(azureServiceBusConfig.getMaxConcurrentCalls()).thenReturn(maxConcurrentCalls); + lenient().when(azureServiceBusConfig.getNThreads()).thenReturn(nThreads); + lenient().when(azureServiceBusConfig.getMaxLockRenewDurationInSeconds()).thenReturn(maxLockRenewDuration); + lenient().when(azureServiceBusConfig.getInitialSubscriptionManagerDelay()).thenReturn(initial_thread_delay); + lenient().when(azureServiceBusConfig.getConsecutiveSubscriptionManagerDelay()).thenReturn(thread_delay); + lenient().when(azureCosmosProperties.registerSubscriptionContainerName()).thenReturn(registerContainerName); + lenient().when(azureCosmosProperties.cosmosDBName()).thenReturn(cosmosDbName); + lenient().when(tenantFactory.listTenantInfo()).thenReturn(Collections.singletonList(tenantInfo)); + lenient().when(cosmosStore.findAllItems(dataPartition, cosmosDbName, registerContainerName, Subscription.class)). + thenReturn(Collections.singletonList(getHmac_subscription())); + } + + @Test + public void shouldSuccessfullyRegisterMessageHandler() throws ServiceBusException, InterruptedException { + + when(subscriptionClientFactory.getSubscriptionClient(dataPartition, sbTopic, sbSubscription)).thenReturn(subscriptionClient); + doNothing().when(subscriptionClient).registerMessageHandler(any(), any(), any()); + subscriptionManager.subscribeNotificationsEvent(); + + verify(azureServiceBusConfig, times(1)).getMaxConcurrentCalls(); + verify(azureServiceBusConfig, times(1)).getNThreads(); + verify(azureServiceBusConfig, times(1)).getMaxLockRenewDurationInSeconds(); + verify(azureCosmosProperties, times(1)).registerSubscriptionContainerName(); + verify(azureCosmosProperties, times(1)).cosmosDBName(); + verify(subscriptionClientFactory, times(1)).getSubscriptionClient(dataPartition, sbTopic, sbSubscription); + verify(subscriptionClient, times(1)).registerMessageHandler(any(), any(), any()); + + } + + @Test + public void shouldNotRegisterMessageHandlerWhenAlreadyRegisteredInPrevThread() throws ServiceBusException, InterruptedException { + + when(subscriptionClientFactory.getSubscriptionClient(dataPartition, sbTopic, sbSubscription)).thenReturn(subscriptionClient); + doNothing().when(subscriptionClient).registerMessageHandler(any(), any(), any()); + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(subscriptionManager, Integer.parseUnsignedInt(azureServiceBusConfig.getInitialSubscriptionManagerDelay()), + Integer.parseUnsignedInt(azureServiceBusConfig.getConsecutiveSubscriptionManagerDelay()), TimeUnit.SECONDS); + sleep(10000); + verify(azureServiceBusConfig, times(1)).getMaxConcurrentCalls(); + verify(azureServiceBusConfig, atLeast(2)).getNThreads(); + verify(azureServiceBusConfig, times(1)).getMaxLockRenewDurationInSeconds(); + verify(azureCosmosProperties, atLeast(2)).registerSubscriptionContainerName(); + verify(azureCosmosProperties, atLeast(2)).cosmosDBName(); + verify(subscriptionClientFactory, times(1)).getSubscriptionClient(dataPartition, sbTopic, sbSubscription); + verify(subscriptionClient, times(1)).registerMessageHandler(any(), any(), any()); + + } + + @Test + public void shouldNotRegisterMessageHandlerIfSSubscriptionClientThrowsException() throws ServiceBusException, InterruptedException { + lenient().doNothing().when(subscriptionClient).registerMessageHandler(any(), any(), any()); + doThrow(new InterruptedException(errorMessage)).when(subscriptionClientFactory).getSubscriptionClient(dataPartition, sbTopic, sbSubscription); + subscriptionManager.subscribeNotificationsEvent(); + + verify(azureServiceBusConfig, times(0)).getMaxConcurrentCalls(); + verify(azureServiceBusConfig, times(1)).getNThreads(); + verify(azureServiceBusConfig, times(0)).getMaxLockRenewDurationInSeconds(); + verify(azureCosmosProperties, times(1)).registerSubscriptionContainerName(); + verify(azureCosmosProperties, times(1)).cosmosDBName(); + verify(subscriptionClientFactory, times(1)).getSubscriptionClient(dataPartition, sbTopic, sbSubscription); + verify(subscriptionClient, times(0)).registerMessageHandler(any(), any(), any()); + } + + @Test + public void shouldThrowExceptionIfErrorWhileRegisteringMessageHandler() throws ServiceBusException, InterruptedException { + + doThrow(new InterruptedException(errorMessage)).when(subscriptionClient).registerMessageHandler(any(), any(), any()); + when(subscriptionClientFactory.getSubscriptionClient(dataPartition, sbTopic, sbSubscription)).thenReturn(subscriptionClient); + subscriptionManager.subscribeNotificationsEvent(); + + verify(azureServiceBusConfig, times(1)).getMaxConcurrentCalls(); + verify(azureServiceBusConfig, times(1)).getNThreads(); + verify(azureServiceBusConfig, times(1)).getMaxLockRenewDurationInSeconds(); + verify(azureCosmosProperties, times(1)).registerSubscriptionContainerName(); + verify(azureCosmosProperties, times(1)).cosmosDBName(); + verify(subscriptionClientFactory, times(1)).getSubscriptionClient(dataPartition, sbTopic, sbSubscription); + verify(subscriptionClient, times(1)).registerMessageHandler(any(), any(), any()); + } + + private static Subscription getHmac_subscription() { + Subscription hmac_subscription = new Subscription(); + hmac_subscription.setName("hamc_test_subscription"); + hmac_subscription.setPushEndpoint("http://challenge"); + hmac_subscription.setDescription("Description"); + hmac_subscription.setTopic(sbTopic); + hmac_subscription.setNotificationId(sbSubscription); + hmac_subscription.setId("id_1"); + hmac_subscription.setCreatedBy("test@test.com"); + HmacSecret secret = new HmacSecret(); + secret.setValue("testsecret"); + hmac_subscription.setSecret(secret); + return hmac_subscription; + } +} diff --git a/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/provider/azure/messageBus/EventGridServiceBusRequestBodyExtractorTest.java b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/provider/azure/messageBus/EventGridServiceBusRequestBodyExtractorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..172cb1a1137012d28b307d4e6b30e918d1cac1db --- /dev/null +++ b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/provider/azure/messageBus/EventGridServiceBusRequestBodyExtractorTest.java @@ -0,0 +1,119 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus; + +import com.microsoft.azure.servicebus.IMessage; +import com.microsoft.azure.servicebus.Message; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.notification.provider.azure.messageBus.extractor.EventGridServiceBusRequestBodyExtractor; +import org.springframework.http.HttpStatus; + +import java.util.Map; + +import static org.junit.Assert.fail; + +@RunWith(MockitoJUnitRunner.class) +public class EventGridServiceBusRequestBodyExtractorTest { + @InjectMocks + private EventGridServiceBusRequestBodyExtractor sut; + private static final String inValidData = "[{\"invalidData\"}]"; + private static final String validData = "[{\"id\":\"opendes:doc:\",\"kind\":\"opendes:at:wellbore:1.0.0\",\"op\":\"create\"},{\"id\":\"opendes:doc:\",\"kind\":\"opendes:at:wellbore:1.0.0\",\"op\":\"create\"}]"; + private static final String dataPartitionId = "opendes"; + private static final String correlationId = "908fcf8d-30c5-4c74-a0ae-ab47b48b7a85"; + private static final String accountId = "ab47b48b7a85-30c5"; + + @Test + public void should_throwWhenAttributesAreMissing_extractDataFromRequestBody() { + IMessage message = getInvalidMessage(); + try { + sut.InitializeExtractor(message); + fail("Should Throw Exception"); + } catch (AppException appException) { + Assert.assertEquals(HttpStatus.BAD_REQUEST.value(), appException.getError().getCode()); + Assert.assertEquals("Unable to parse request payload.", appException.getError().getMessage()); + } catch (Exception exception) { + fail("Should Throw AppException"); + } + + } + + @Test + public void shouldReturnNotificationDataAndAttributesWhenValidRequestBody() { + IMessage message = getValidMessage(); + try { + sut.InitializeExtractor(message); + String notificationData = sut.extractDataFromRequestBody(); + Assert.assertEquals(notificationData, validData); + Map<String, String> attributes = sut.extractAttributesFromRequestBody(); + Assert.assertEquals(attributes.get("account-id"), accountId); + Assert.assertEquals(attributes.get("correlation-id"), correlationId); + Assert.assertEquals(attributes.get("data-partition-id"), dataPartitionId); + + } catch (Exception exception) { + fail("Should not Throw AppException"); + } + + } + + private Message getValidMessage() { + + String body = + "[\n" + + " {\n" + + " \"id\": \"2425\",\n" + + " \"eventType\": \"recordInserted\",\n" + + " \"subject\": \"myapp/vehicles/motorcycles\",\n" + + " \"data\": {\n" + + " \"data\":" + validData + ",\n" + + " \"account-id\": \"" + accountId + "\",\n" + + " \"correlation-id\": \"" + correlationId + "\",\n" + + " \"data-partition-id\": \"" + dataPartitionId + "\"\n" + + " },\n" + + " \"dataVersion\": \"1.0\",\n" + + " \"metadataVersion\": \"1\",\n" + + " \"eventTime\": \"2020-08-14T18:04:06\",\n" + + " \"topic\": \"records-changed\"\n" + + " }\n" + + "]"; + return new Message(body); + } + + private Message getInvalidMessage() { + + String body = + "[\n" + + " {\n" + + " \"id\": \"2425\",\n" + + " \"eventType\": \"recordInserted\",\n" + + " \"subject\": \"myapp/vehicles/motorcycles\",\n" + + " \"data\": {\n" + + " \"data\":" + inValidData + ",\n" + + " \"data-partition-id\": \"" + dataPartitionId + "\"\n" + + " },\n" + + " \"dataVersion\": \"1.0\",\n" + + " \"metadataVersion\": \"1\",\n" + + " \"eventTime\": \"2020-08-14T18:04:12+00:00\",\n" + + " \"topic\": \"records-changed\"\n" + + " }\n" + + "]"; + Message message = new Message(body); + return message; + } +} diff --git a/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/provider/azure/messageBus/ServiceBusRequestBodyExtractorTest.java b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/provider/azure/messageBus/ServiceBusRequestBodyExtractorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6f9729ecd25dabd2118d8adf33643928587e1c96 --- /dev/null +++ b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/provider/azure/messageBus/ServiceBusRequestBodyExtractorTest.java @@ -0,0 +1,101 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.provider.azure.messageBus; + +import com.microsoft.azure.servicebus.IMessage; +import com.microsoft.azure.servicebus.Message; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.notification.provider.azure.messageBus.extractor.ServiceBusRequestBodyExtractor; +import org.springframework.http.HttpStatus; + +import java.util.Map; + +import static org.junit.Assert.fail; + +@RunWith(MockitoJUnitRunner.class) +public class ServiceBusRequestBodyExtractorTest { + @InjectMocks + private ServiceBusRequestBodyExtractor sut; + private static final String inValidData = "[{\"invalidData\"}]"; + private static final String validData = "[{\"id\":\"opendes:doc:\",\"kind\":\"opendes:at:wellbore:1.0.0\",\"op\":\"create\"},{\"id\":\"opendes:doc:\",\"kind\":\"opendes:at:wellbore:1.0.0\",\"op\":\"create\"}]"; + private static final String dataPartitionId = "opendes"; + private static final String correlationId = "908fcf8d-30c5-4c74-a0ae-ab47b48b7a85"; + private static final String accountId = "ab47b48b7a85-30c5"; + + @Test + public void should_throwWhenAttributesAreMissing_extractDataFromRequestBody() { + IMessage message = getInvalidMessage(); + try { + sut.InitializeExtractor(message); + fail("Should Throw Exception"); + } catch (AppException appException) { + Assert.assertEquals(HttpStatus.BAD_REQUEST.value(), appException.getError().getCode()); + Assert.assertEquals("Unable to parse request payload.", appException.getError().getMessage()); + } catch (Exception exception) { + fail("Should Throw AppException"); + } + + } + + @Test + public void shouldReturnNotificationDataAndAttributesWhenValidRequestBody() { + IMessage message = getValidMessage(); + try { + sut.InitializeExtractor(message); + String notificationData = sut.extractDataFromRequestBody(); + Assert.assertEquals(notificationData, validData); + Map<String, String> attributes = sut.extractAttributesFromRequestBody(); + Assert.assertEquals(attributes.get("account-id"), accountId); + Assert.assertEquals(attributes.get("correlation-id"), correlationId); + Assert.assertEquals(attributes.get("data-partition-id"), dataPartitionId); + + } catch (Exception exception) { + fail("Should not Throw AppException"); + } + + } + + private Message getValidMessage() { + + String body = + " {\n" + + " \"message\": {\n" + + " \"data\":" + validData + ",\n" + + " \"account-id\": \"" + accountId + "\",\n" + + " \"correlation-id\": \"" + correlationId + "\",\n" + + " \"data-partition-id\": \"" + dataPartitionId + "\"\n" + + " }\n" + + " }"; + return new Message(body); + } + + private Message getInvalidMessage() { + String body = + " {\n" + + " \"message\": {\n" + + " \"data\":" + inValidData + ",\n" + + " \"correlation-id\": \"" + correlationId + "\",\n" + + " \"data-partition-id\": \"" + dataPartitionId + "\"\n" + + " }\n" + + " }"; + Message message = new Message(body); + return message; + } +} diff --git a/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/util/ServiceAccountClientImplTest.java b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/util/ServiceAccountClientImplTest.java index e7caf1d3bf93c6e74b665570e48cb94a0d5c7f3d..59fbfb854e7e3bca9ea106a1706b387890f74e1b 100644 --- a/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/util/ServiceAccountClientImplTest.java +++ b/provider/notification-azure/src/test/java/org/opengroup/osdu/notification/util/ServiceAccountClientImplTest.java @@ -14,104 +14,56 @@ package org.opengroup.osdu.notification.util; -import org.apache.http.HttpStatus; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnitRunner; -import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.mockito.junit.jupiter.MockitoExtension; +import org.omg.CORBA.portable.ApplicationException; +import org.opengroup.osdu.azure.util.AzureServicePrincipleTokenService; import org.opengroup.osdu.core.common.model.http.AppException; -import org.opengroup.osdu.core.common.model.search.IdToken; -import org.opengroup.osdu.notification.provider.azure.cache.JwtCache; -import org.opengroup.osdu.notification.provider.azure.util.AppProperties; import org.opengroup.osdu.notification.provider.azure.util.ServiceAccountJwtAzureClientImpl; -import java.util.concurrent.ExecutorService; +import java.io.UnsupportedEncodingException; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class ServiceAccountClientImplTest { - final String tenantName = "Test Tenant"; - final String validToken = "validToken"; - - @Mock - private IdToken idToken; - - @Mock - private ExecutorService executorService; - - @Mock - private AppProperties appProperties; - - @Mock - private JwtCache tenantJwtCacheMock; - - @Mock - private JaxRsDpsLog logger; + private static final String tenantId = "tenantId"; + private static final String token = "jwt-token"; @InjectMocks - @Spy - private ServiceAccountJwtAzureClientImpl sut; + private ServiceAccountJwtAzureClientImpl serviceAccountJwtAzureClient; - @Before - public void setup() { - initMocks(this); - idToken = IdToken.builder().tokenValue(validToken).expirationTimeMillis(System.currentTimeMillis() + 10000000L).build(); - } + @Mock + private AzureServicePrincipleTokenService azureServicePrincipleTokenService; @Test - public void should_getTokenFromCache_getIdTokenTest() { - // SetUp - when(tenantJwtCacheMock.get(any())).thenReturn(idToken); - String expectedToken = "Bearer " +idToken.getTokenValue(); + public void shouldSuccessfullyGenerateToken() throws UnsupportedEncodingException, ApplicationException { - // Act - String returnedIdToken = sut.getIdToken(tenantName); + when(azureServicePrincipleTokenService.getAuthorizationToken()).thenReturn(token); - // Assert - Assert.assertEquals(expectedToken, returnedIdToken); + String result = serviceAccountJwtAzureClient.getIdToken(tenantId); + + assertEquals("Bearer " + token, result); + verify(azureServicePrincipleTokenService, times(1)).getAuthorizationToken(); } @Test - public void should_updateCache_getIdTokenTest() { - // Set up - when(tenantJwtCacheMock.get(any())).thenReturn(idToken); - String expectedToken = "Bearer " +idToken.getTokenValue(); + public void shouldThrowAppException() throws UnsupportedEncodingException { - // Act - String returnedToken = this.sut.getIdToken(tenantName); + doThrow(AppException.class).when(azureServicePrincipleTokenService).getAuthorizationToken(); - // Assert - Assert.assertEquals(expectedToken, returnedToken); - } + AppException exception = assertThrows(AppException.class, () -> { + serviceAccountJwtAzureClient.getIdToken(tenantId); + }); - @Test - public void should_return403GivenInvalidApplicationProperties_getAccessToken() { - when(appProperties.getAuthURL()).thenReturn("https://login.microsoftonline.com/s/oauth2/token/"); - when(appProperties.getAuthClientID()).thenReturn("testAuthClientID"); - when(appProperties.getAuthClientSecret()).thenReturn("testAuthClientSecret"); - when(appProperties.getAadClientID()).thenReturn("testAadClientID"); - - try { - // Act - sut.getAccessToken(executorService); - - // Assert - fail("Should throw exception"); - } catch (AppException appException) { - Assert.assertEquals(HttpStatus.SC_FORBIDDEN, appException.getError().getCode()); - } catch (Exception e) { - fail("Should not throw this exception" + e.getMessage()); - } + assertNotNull(exception); + verify(azureServicePrincipleTokenService, times(1)).getAuthorizationToken(); } } diff --git a/provider/notification-gcp/README.md b/provider/notification-gcp/README.md index 28510ea1aa16804a313b26f988a32a49caa897b8..80a59b3287f04ee392f40d1e1d83fd8b0dc23384 100644 --- a/provider/notification-gcp/README.md +++ b/provider/notification-gcp/README.md @@ -31,6 +31,7 @@ In order to run the service locally or remotely, you will need to have the follo | `APP_REGISTER` | ex `https://register.com/api/register/v1` | Storage API endpoint | no | output of infrastructure deployment | | `APP_PROJECT` | ex `opendes` | Google Cloud Project Id | no | output of infrastructure deployment | | `APP_AUDIENCES` | ex `*****.apps.googleusercontent.com` | Client ID for getting access to cloud resources | yes | https://console.cloud.google.com/apis/credentials | +| `PARTITION_API` | ex `http://localhost:8081/api/partition/v1` | Partition service endpoint | no | - | **System Environment required to run service** @@ -135,7 +136,7 @@ After the service has started it should be accessible via a web browser by visit | `ENVIRONMENT` | `dev` OR `local` OR `dev_gke`| Local for running locally with services url's predefined as http://localhost , Dev & Dev_Gke is configurable environment | no | - | | `HMAC_SECRET` | ex`7a786376626e` | String in hex , must match pattern ^[a-zA-Z0-9]{8,30}+$ & be in register variable SUBSCRIBER_SECRET | yes | - | | `REGISTER_BASE_URL` | `http://localhost:8081/api/register/v1` | Register service url | no | - | -| `NOTIFICATION_BASE_URL` | `http://localhost:8080/` | Notification service url | no | - | +| `NOTIFICATION_BASE_URL` | `http://localhost:8080/api/notification/v1/` | Notification service url | no | - | | `INTEGRATION_TEST_AUDIENCE` | `********` | Client application ID | yes | https://console.cloud.google.com/apis/credentials | | `CLIENT_TENANT` | ex `opendes` | Client tenant | no | - | | `OSDU_TENANT` | ex `osdu` | Osdu tenant | no | - | diff --git a/provider/notification-gcp/pom.xml b/provider/notification-gcp/pom.xml index 399ab228ac2a2b3acaf702cb8b2316d6ce7db9da..a86b437ba4371a20cf5b1e3e9d70b00773d9f8bf 100644 --- a/provider/notification-gcp/pom.xml +++ b/provider/notification-gcp/pom.xml @@ -18,7 +18,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-gcp</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-gcp</name> <description>GCP implementation for Notification service</description> <packaging>jar</packaging> @@ -26,7 +26,7 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../../pom.xml</relativePath> </parent> @@ -44,13 +44,13 @@ <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>core-lib-gcp</artifactId> - <version>0.7.0</version> + <version>0.11.0</version> </dependency> <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> </dependency> <dependency> diff --git a/provider/notification-gcp/src/main/java/org/opengroup/osdu/notification/provider/gcp/di/ServiceAccountJwtClientFactory.java b/provider/notification-gcp/src/main/java/org/opengroup/osdu/notification/provider/gcp/di/ServiceAccountJwtClientFactory.java index 403523702216a70169fdc77a96fef3d4490e3580..ad4a541f081362e8a46fdb84e5af5dcb6a13e381 100644 --- a/provider/notification-gcp/src/main/java/org/opengroup/osdu/notification/provider/gcp/di/ServiceAccountJwtClientFactory.java +++ b/provider/notification-gcp/src/main/java/org/opengroup/osdu/notification/provider/gcp/di/ServiceAccountJwtClientFactory.java @@ -20,9 +20,11 @@ import org.opengroup.osdu.notification.provider.gcp.util.AppProperties; import org.opengroup.osdu.notification.provider.gcp.util.ServiceAccountJwtGcpClientImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component +@Primary public class ServiceAccountJwtClientFactory extends AbstractFactoryBean<IServiceAccountJwtClient> { @Autowired diff --git a/provider/notification-gcp/src/main/resources/application.properties b/provider/notification-gcp/src/main/resources/application.properties index fa6abe7faedd3e9971e6f725712c99e80a1788c8..c2f3bf7bea18040d814d894a14b5b50ec671e6ad 100644 --- a/provider/notification-gcp/src/main/resources/application.properties +++ b/provider/notification-gcp/src/main/resources/application.properties @@ -16,7 +16,10 @@ LOG_PREFIX=notification logging.level.org.springframework.web=${LOG_LEVEL:DEBUG} -server.servlet.contextPath=/ +server.servlet.contextPath=/api/notification/v1 app.expireTime=300 app.maxCacheSize=10 -server.error.whitelabel.enabled=false \ No newline at end of file +server.error.whitelabel.enabled=false + +google.audiences=${APP_AUDIENCES} +partition.api=http://localhost:8081/api/partition/v1 \ No newline at end of file diff --git a/provider/notification-ibm/pom.xml b/provider/notification-ibm/pom.xml index ceb68a4d42a4752eade4204f1e41f856f514557e..089f6a66c5f698db00353a63f1e512779929c7e8 100644 --- a/provider/notification-ibm/pom.xml +++ b/provider/notification-ibm/pom.xml @@ -16,7 +16,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-ibm</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-ibm</name> <description>IBM implementation for Notification service</description> <packaging>jar</packaging> @@ -24,7 +24,7 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../../pom.xml</relativePath> </parent> @@ -44,13 +44,13 @@ <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>os-core-lib-ibm</artifactId> - <version>0.7.0</version> + <version>0.9.0</version> </dependency> <dependency> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> </dependency> <!-- unit test dependencies --> diff --git a/provider/notification-ibm/src/main/java/org/opengroup/osdu/notification/provider/ibm/security/SecurityConfig.java b/provider/notification-ibm/src/main/java/org/opengroup/osdu/notification/provider/ibm/security/SecurityConfig.java index 593efbbbf15e985a780b2a0cd568449d2372c0ae..bfa018bdf7ae35152d42b234aa2eb248280477fe 100644 --- a/provider/notification-ibm/src/main/java/org/opengroup/osdu/notification/provider/ibm/security/SecurityConfig.java +++ b/provider/notification-ibm/src/main/java/org/opengroup/osdu/notification/provider/ibm/security/SecurityConfig.java @@ -20,6 +20,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { "/swagger-resources/**", "/configuration/security", "/swagger-ui.html", + "/info", "/webjars/**") .permitAll().anyRequest().authenticated().and().oauth2ResourceServer().jwt(); } diff --git a/provider/notification-reference/README.md b/provider/notification-reference/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6cfa317806a48ecb47185d38bcfd40a3fc7e70a3 --- /dev/null +++ b/provider/notification-reference/README.md @@ -0,0 +1,184 @@ +# Notification Service +notification-reference is a [Spring Boot](https://spring.io/projects/spring-boot) service that allow for interested consumers to subscribe to data and metadata changes using a publish/subscriber pattern. +This service could be used for OSDU hybrid cloud. + +## Getting Started +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. + +### Requirements +* Java 8 +* [Maven 3.6.0+](https://maven.apache.org/download.cgi) +* GCloud command line tool +* GCloud access to opendes project + +### General Tips + +**Environment Variable Management** +The following tools make environment variable configuration simpler + - [direnv](https://direnv.net/) - for a shell/terminal environment + - [EnvFile](https://plugins.jetbrains.com/plugin/7861-envfile) - for [Intellij IDEA](https://www.jetbrains.com/idea/) + +**Lombok** +This project uses [Lombok](https://projectlombok.org/) for code generation. You may need to configure your IDE to take advantage of this tool. + - [Intellij configuration](https://projectlombok.org/setup/intellij) + - [VSCode configuration](https://projectlombok.org/setup/vscode) + +### Installation +In order to run the service locally or remotely, you will need to have the following environment variables defined. + +| name | value | description | sensitive? | source | +| --- | --- | --- | --- | --- | +| `APP_ENTITLEMENTS` | ex `https://entitlements.com/entitlements/v1` | Entitlements API endpoint | no | output of infrastructure deployment | +| `APP_REGISTER` | ex `https://register.com/api/register/v1` | Storage API endpoint | no | output of infrastructure deployment | +| `APP_PROJECT` | ex `opendes` | Google Cloud Project Id | no | output of infrastructure deployment | +| `APP_AUDIENCES` | ex `*****.apps.googleusercontent.com` | Client ID for getting access to cloud resources | yes | https://console.cloud.google.com/apis/credentials | +| `PARTITION_API` | ex `http://localhost:8081/api/partition/v1` | Partition service endpoint | no | - | + +**System Environment required to run service** + +| name | value | description | sensitive? | source | +| --- | --- | --- | --- | --- | +| `SPRING_PROFILES_ACTIVE` | `local` | spring active profile | no | + +### Run Locally +Check that maven is installed: +```bash +$ mvn --version +Apache Maven 3.6.0 +Maven home: /usr/share/maven +Java version: 1.8.0_212, vendor: AdoptOpenJDK, runtime: /usr/lib/jvm/jdk8u212-b04/jre +... +``` + +You will need to configure access to the remote maven repository that holds the OSDU dependencies. This file should live within `~/.m2/settings.xml`: + +```bash +$ cat ~/.m2/settings.xml +<?xml version="1.0" encoding="UTF-8"?> +<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> + <servers> + <server> + <id>os-core</id> + <username>slb-des-ext-collaboration</username> + <!-- Treat this auth token like a password. Do not share it with anyone, including Microsoft support. --> + <password>${VSTS_FEED_TOKEN}</password> + </server> + </servers> +</settings> +``` + +* Update the Google cloud SDK to the latest version: + +```bash +gcloud components update +``` +* Set Google Project Id: + +```bash +gcloud config set project <YOUR-PROJECT-ID> +``` + +* Perform a basic authentication in the selected project: + +```bash +gcloud auth application-default login +``` + +* Navigate to notification service's root folder and run: + +```bash +mvn jetty:run +## Testing +* Navigate to notification service's root folder and run: + +```bash +mvn clean install +``` + +* If you wish to see the coverage report then go to testing/target/site/jacoco-aggregate and open index.html + +* If you wish to build the project without running tests + +```bash +mvn clean install -DskipTests +``` + +After configuring your environment as specified above, you can follow these steps to build and run the application. These steps should be invoked from the *repository root.* + +```bash +cd provider/notification-reference/ && mvn spring-boot:run -Dspring-boot.run.profiles=local +``` + +## Testing +Navigate to notification service's root folder and run all the tests: + +```bash +# build + test + install core service code +$ (cd notification-core/ && mvn clean install) +``` + +## Test the application + +After the service has started it should be accessible via a web browser by visiting [http://localhost:8080/api/notification/v1/swagger-ui.html](http://localhost:8080/swagger-ui.html). If the request does not fail, you can then run the integration tests. + +### Dependencies needed to run the integration tests +* Java 8 +* Maven +* Values for the following environment variables in Config.java + +| name | value | description | sensitive? | source | +| --- | --- | --- | --- | --- | +| `DE_OPS_TESTER` | `*****` | Service account base64 encoded string for API calls. Note: this user must have entitlements configured already, also **Private key id** of this account must be set in Register service variable SUBSCRIBER_PRIVATE_KEY_ID | yes | https://console.cloud.google.com/iam-admin/serviceaccounts | +| `DE_ADMIN_TESTER` | `*****` | Service account base64 encoded string for API calls. Note: this user must have entitlements configured already | yes | https://console.cloud.google.com/iam-admin/serviceaccounts | +| `DE_EDITOR_TESTER` | `*****` | Service account base64 encoded string for API calls. Note: this user must have entitlements configured already | yes | https://console.cloud.google.com/iam-admin/serviceaccounts | +| `DE_NO_ACCESS_TESTER` | `*****` | Service account base64 encoded string for API calls. Note: this user must have entitlements configured already | yes | https://console.cloud.google.com/iam-admin/serviceaccounts | +| `ENVIRONMENT` | `dev` OR `local` OR `dev_gke`| Local for running locally with services url's predefined as http://localhost , Dev & Dev_Gke is configurable environment | no | - | +| `HMAC_SECRET` | ex`7a786376626e` | String in hex , must match pattern ^[a-zA-Z0-9]{8,30}+$ & be in register variable SUBSCRIBER_SECRET | yes | - | +| `REGISTER_BASE_URL` | `http://localhost:8081/api/register/v1` | Register service url | no | - | +| `NOTIFICATION_BASE_URL` | `http://localhost:8080/api/notification/v1/` | Notification service url | no | - | +| `INTEGRATION_TEST_AUDIENCE` | `********` | Client application ID | yes | https://console.cloud.google.com/apis/credentials | +| `CLIENT_TENANT` | ex `opendes` | Client tenant | no | - | +| `OSDU_TENANT` | ex `osdu` | Osdu tenant | no | - | +| `TOPIC_ID` | ex `records-changed` | PubSub topic id | no | https://console.cloud.google.com/cloudpubsub/topic | +| `REGISTER_CUSTOM_PUSH_URL_HMAC` | ex `http://localhost:8081/api/register/v1/test/challenge/hmac-integration-test` | Register testing push url | no | - | + + **Entitlements configuration for integration accounts** + + | DE_OPS_TESTER | DE_ADMIN_TESTER | DE_EDITOR_TESTER | DE_NO_ACCESS_TESTER | + | --- | --- | --- | --- | + |notification.pubsub<br/>service.entitlements.user<br/>users<br/>users.datalake.ops</br>| service.entitlements.user<br/>users<br/>users.datalake.admins</br> | service.entitlements.user<br/>users<br/>users.datalake.editors</br> | service.entitlements.user<br/>users<br/>| + +Above variables should be configured in the release pipeline to run integration tests. You should also replace them with proper values if you wish to run tests locally. + +### Commands to run tests +* Integration tests are refactored into two pieces: Core and Provider. Core contains business logic for tests and is a dependency for executing the tests from provider module. To build the core module, simply navigate to `notification-test-core` directory and run `mvn clean install`. This will build the core module +* Next, to execute the integration tests, navigate to the provider module and execute `mvn test` +```bash +# (cd testing/notification-test-core/ && mvn clean install) +# Note: this assumes that the environment variables for integration tests as outlined +# above are already exported in your environment. +$ (cd testing/notification-test-gcp/ && mvn clean test) +``` + +## Deployment +GKE Google Documentation: https://cloud.google.com/build/docs/deploying-builds/deploy-gke +Anthos Google Documentation: https://cloud.google.com/anthos/multicluster-management/gateway/tutorials/cloud-build-integration + +## License +Copyright © Google LLC + +Copyright © EPAM Systems + +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](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. \ No newline at end of file diff --git a/provider/notification-reference/kubernetes/deployments/deployment-os-notification-service.yml b/provider/notification-reference/kubernetes/deployments/deployment-os-notification-service.yml new file mode 100644 index 0000000000000000000000000000000000000000..ff445d3cdade8484695dd3f9e57ea88d66af7481 --- /dev/null +++ b/provider/notification-reference/kubernetes/deployments/deployment-os-notification-service.yml @@ -0,0 +1,81 @@ +apiVersion: v1 +data: + APP_ENTITLEMENTS: ${APP_ENTITLEMENTS} + APP_REGISTER: ${APP_REGISTER} + APP_PROJECT: ${APP_PROJECT} + APP_AUDIENCES: ${APP_AUDIENCES} + PARTITION_API: ${PARTITION_API} + +kind: ConfigMap +metadata: + labels: + app: notification-reference + name: notification-config + namespace: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + generateName: notification-reference-anthos + labels: + app: notification-reference + name: notification-reference + namespace: default +spec: + selector: + matchLabels: + app: notification-reference + replicas: 1 + template: + metadata: + labels: + app: notification-reference + spec: + containers: + - env: + - name: APP_ENTITLEMENTS + valueFrom: + configMapKeyRef: + key: APP_ENTITLEMENTS + name: notification-config + - name: APP_REGISTER + valueFrom: + configMapKeyRef: + key: APP_REGISTER + name: notification-config + - name: LOG_LEVEL + valueFrom: + configMapKeyRef: + key: LOG_LEVEL + name: notification-config + - name: APP_PROJECT + valueFrom: + configMapKeyRef: + key: APP_PROJECT + name: notification-config + - name: APP_AUDIENCES + valueFrom: + configMapKeyRef: + key: APP_AUDIENCES + name: notification-config + - name: PARTITION_API + valueFrom: + configMapKeyRef: + key: PARTITION_API + name: notification-config + image: us.gcr.io/osdu-anthos-02/os-notification/anthos-notification-reference:9966597-dirty + name: notification-reference +--- +apiVersion: v1 +kind: Service +metadata: + name: notification-reference + namespace: default +spec: + ports: + - protocol: TCP + port: 80 + targetPort: 8080 + selector: + app: notification-reference + type: LoadBalancer \ No newline at end of file diff --git a/provider/notification-reference/pom.xml b/provider/notification-reference/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..cd5610ffa7fdd0a6db36e08998ed93bdda91674d --- /dev/null +++ b/provider/notification-reference/pom.xml @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.opengroup.osdu</groupId> + <artifactId>os-notification</artifactId> + <version>0.12.0-SNAPSHOT</version> + <relativePath>../../pom.xml</relativePath> + </parent> + + <groupId>org.opengroup.osdu</groupId> + <artifactId>notification-reference</artifactId> + <version>0.12.0-SNAPSHOT</version> + <packaging>jar</packaging> + + <properties> + <java.version>8</java.version> + <maven.compiler.target>${java.version}</maven.compiler.target> + <maven.compiler.source>${java.version}</maven.compiler.source> + </properties> + + <dependencies> + <dependency> + <groupId>org.opengroup.osdu</groupId> + <artifactId>core-lib-gcp</artifactId> + <version>0.11.0</version> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-amqp</artifactId> + </dependency> + + <dependency> + <groupId>org.opengroup.osdu</groupId> + <artifactId>notification-core</artifactId> + <version>0.12.0-SNAPSHOT</version> + </dependency> + + <dependency> + <groupId>org.opengroup.osdu</groupId> + <artifactId>os-core-common</artifactId> + </dependency> + + <dependency> + <groupId>ch.qos.logback.contrib</groupId> + <artifactId>logback-json-classic</artifactId> + <version>0.1.5</version> + </dependency> + + <!-- unit test dependencies --> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito2</artifactId> + <version>2.0.2</version> + <scope>test</scope> + </dependency> + <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-all</artifactId> + <version>2.0.2-beta</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.12</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4</artifactId> + <version>2.0.2</version> + <scope>test</scope> + </dependency> + + </dependencies> + + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <configuration> + <profiles> + <profile> + <id>local</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <properties> + <spring.profiles.active>local</spring.profiles.active> + </properties> + </profile> + <profile> + <id>dev</id> + <properties> + <spring.profiles.active>dev</spring.profiles.active> + </properties> + </profile> + </profiles> + </configuration> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + <configuration> + <classifier>spring-boot</classifier> + <mainClass> + org.opengroup.osdu.notification.provider.reference.Application + </mainClass> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-war-plugin</artifactId> + <configuration> + <failOnMissingWebXml>false</failOnMissingWebXml> + </configuration> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.7.7.201606060606</version> + <executions> + <execution> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>prepare-package</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> \ No newline at end of file diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/Application.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/Application.java new file mode 100644 index 0000000000000000000000000000000000000000..e7639be09c10c143fd035434c5f62f9f53d2bcda --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/Application.java @@ -0,0 +1,40 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.FilterType; +import org.springframework.scheduling.annotation.EnableAsync; + +@SpringBootApplication +@ComponentScan(value = {"org.opengroup.osdu"}, excludeFilters = { + @Filter( + type = FilterType.REGEX, + pattern = {"org.opengroup.osdu.core.gcp.multitenancy.StorageFactory"} + ) +}) +@EnableAsync +public class Application { + + public static void main(String[] args) { + SpringApplication.run(new Class[]{Application.class}, args); + } +} \ No newline at end of file diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/config/PropertiesConfiguration.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/config/PropertiesConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..c34830e3c279a9bcaca126ba82efae448752618a --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/config/PropertiesConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties +@Data +public class PropertiesConfiguration { + + private String authorizeAPI; + private String registerAPI; + private String projectId; + private Integer expireTime = 300; + private Integer maxCacheSize = 10; + + private String googleCloudProject; + private String googleCloudProjectRegion; + private String googleAudiences; +} diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/pubsub/PubsubHandshakeHandler.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/pubsub/PubsubHandshakeHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..6e3d7e7dd1f9cfbf2a0f904b68653f089a66cb3c --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/pubsub/PubsubHandshakeHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.pubsub; + +import org.opengroup.osdu.notification.provider.interfaces.IPubsubHandshakeHandler; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +@Component +@Lazy +public class PubsubHandshakeHandler implements IPubsubHandshakeHandler { + + @Override + public String getHandshakeResponse() { + return null; + } +} \ No newline at end of file diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/pubsub/PubsubRequestBodyExtractor.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/pubsub/PubsubRequestBodyExtractor.java new file mode 100644 index 0000000000000000000000000000000000000000..8928a72498717e86317789d054d95eecb65eca31 --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/pubsub/PubsubRequestBodyExtractor.java @@ -0,0 +1,144 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.pubsub; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.servlet.http.HttpServletRequest; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.storage.MessageContent; +import org.opengroup.osdu.notification.provider.interfaces.IPubsubRequestBodyExtractor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.context.annotation.RequestScope; + +@Component +@RequestScope +public class PubsubRequestBodyExtractor implements IPubsubRequestBodyExtractor { + + private static final String INVALID_PUBSUB_MESSAGE = "Invalid pubsub message"; + private static final Gson GSON = new Gson(); + private MessageContent messageContent; + private JsonObject root = null; + + @Autowired + private HttpServletRequest request; + + @Autowired + private JaxRsDpsLog log; + + public Map<String, String> extractAttributesFromRequestBody() { + if (this.messageContent == null) { + this.messageContent = this.extractPubsubMessageFromRequestBody(); + } + return this.messageContent.getAttributes(); + } + + public String extractDataFromRequestBody() { + if (this.messageContent == null) { + this.messageContent = this.extractPubsubMessageFromRequestBody(); + } + return this.messageContent.getData(); + } + + public String extractNotificationIdFromRequestBody() { + if (this.root == null) { + this.root = this.extractRootJsonElementFromRequestBody(); + } + JsonElement subscription = this.root.get("subscription"); + if (subscription == null) { + throw new AppException(HttpStatus.BAD_REQUEST.value(), INVALID_PUBSUB_MESSAGE, + "subscription object not found"); + } + + String[] fullNotificationId = subscription.getAsString().split("/"); + return fullNotificationId[fullNotificationId.length - 1]; + } + + @Override + public boolean isHandshakeRequest() { + return false; + } + + private MessageContent extractPubsubMessageFromRequestBody() { + if (this.root == null) { + this.root = this.extractRootJsonElementFromRequestBody(); + } + JsonElement message = this.root.get("message"); + if (message == null) { + throw new AppException(HttpStatus.BAD_REQUEST.value(), INVALID_PUBSUB_MESSAGE, + "message object not found"); + } + MessageContent content = GSON.fromJson(message.toString(), MessageContent.class); + + Map<String, String> attributes = content.getAttributes(); + if (attributes == null || attributes.isEmpty()) { + log.error("Incorrect Message: " + message.toString()); + throw new AppException(HttpStatus.BAD_REQUEST.value(), INVALID_PUBSUB_MESSAGE, + "attribute map not found"); + } + String data = content.getData(); + if (Strings.isNullOrEmpty(data)) { + throw new AppException(HttpStatus.BAD_REQUEST.value(), INVALID_PUBSUB_MESSAGE, + "data field not found"); + } + Map<String, String> lowerCase = new HashMap<>(); + attributes.forEach((key, value) -> lowerCase.put(key.toLowerCase(), value)); + if (Strings.isNullOrEmpty(attributes.get("data-partition-id"))) { + throw new AppException(HttpStatus.BAD_REQUEST.value(), INVALID_PUBSUB_MESSAGE, + "No tenant information from pubsub message."); + } + content.setAttributes(lowerCase); + + String decoded = new String(Base64.getDecoder().decode(data)); + content.setData(decoded); + + return content; + } + + private JsonObject extractRootJsonElementFromRequestBody() { + try { + JsonParser jsonParser = new JsonParser(); + BufferedReader reader = request.getReader(); + Stream<String> lines = reader.lines(); + String requestBody = lines.collect(Collectors.joining("\n")); + JsonElement rootElement = jsonParser.parse(requestBody); + if (!(rootElement instanceof JsonObject)) { + throw new AppException(HttpStatus.BAD_REQUEST.value(), "RequestBody is not JsonObject.", + "Request Body should be JsonObject to be processed."); + } + return rootElement.getAsJsonObject(); + } catch (IOException e) { + throw new AppException(HttpStatus.INTERNAL_SERVER_ERROR.value(), + "Request payload parsing error", + "Unable to parse request payload.", e); + } + } +} diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/security/SecurityConfig.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/security/SecurityConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..a60a619cdebadfddf4c31a9033587d9a93d5a5b8 --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/security/SecurityConfig.java @@ -0,0 +1,35 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableGlobalMethodSecurity(prePostEnabled = true) +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity httpSecurity) throws Exception { + httpSecurity + .httpBasic().disable() + .csrf().disable(); + } +} \ No newline at end of file diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/AppProperties.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/AppProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..baaa243682eea04f7023ade9cc04c18ef393ccb2 --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/AppProperties.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.util; + +import lombok.RequiredArgsConstructor; +import org.opengroup.osdu.notification.provider.interfaces.IAppProperties; +import org.opengroup.osdu.notification.provider.reference.config.PropertiesConfiguration; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class AppProperties implements IAppProperties { + + private final PropertiesConfiguration propertiesConfiguration; + + public String getAuthorizeAPI() { + return propertiesConfiguration.getAuthorizeAPI(); + } + + public String getRegisterAPI() { + return propertiesConfiguration.getRegisterAPI(); + } + + public String getPubSubServiceAccountEmail() { + return String.format("de-notification-service@%s.iam.gserviceaccount.com", + propertiesConfiguration.getProjectId()); + } + + public String getGoogleAudiences() { + return propertiesConfiguration.getGoogleAudiences(); + } +} diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountImpl.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..7f511a67ed894418932f029b1b4037423042b9b2 --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountImpl.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.util; + +import lombok.SneakyThrows; +import org.apache.http.impl.client.CloseableHttpClient; +import org.opengroup.osdu.core.gcp.GoogleIdToken.IGoogleIdTokenFactory; +import org.opengroup.osdu.notification.provider.interfaces.IGoogleServiceAccount; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class GoogleServiceAccountImpl implements IGoogleServiceAccount { + + @Autowired + private IGoogleIdTokenFactory googleIdTokenFactory; + @Autowired + private CloseableHttpClient closeableHttpClient; + + @SneakyThrows + @Override + public String getIdToken(String keyString, String audience) { + return this.googleIdTokenFactory.getGoogleIdToken(keyString, audience, + this.closeableHttpClient); + } +} \ No newline at end of file diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountValidatorGenerator.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountValidatorGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..462f9012c4ceee99a22bd7d5230bb51ee5a2abb6 --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountValidatorGenerator.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.util; + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import java.util.Arrays; +import org.springframework.stereotype.Component; + +@Component +public class GoogleServiceAccountValidatorGenerator { + + public GoogleIdTokenVerifier getVerifier(NetHttpTransport transport, JacksonFactory factory, + String... googleAudiences) { + GoogleIdTokenVerifier verifier; + if (googleAudiences == null || googleAudiences.length == 0) { + verifier = new GoogleIdTokenVerifier.Builder(transport, factory) + .build(); + } else { + verifier = new GoogleIdTokenVerifier.Builder(transport, factory) + .setAudience(Arrays.asList(googleAudiences)) + .build(); + } + return verifier; + } +} diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountValidatorImpl.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountValidatorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5841f8a7ea7f25eefacc0fda1bd5041fbb68a57a --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/GoogleServiceAccountValidatorImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.util; + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.notification.provider.interfaces.IServiceAccountValidator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class GoogleServiceAccountValidatorImpl implements IServiceAccountValidator { + + private final NetHttpTransport netHttpTransport = new NetHttpTransport(); + private final JacksonFactory jacksonFactory = new JacksonFactory(); + + @Autowired + private JaxRsDpsLog log; + @Autowired + private AppProperties appConfig; + @Autowired + private GoogleServiceAccountValidatorGenerator verifierGenerator; + + @Override + public boolean isValidPublisherServiceAccount(String jwt) { + return isValidServiceAccount(jwt, this.appConfig.getPubSubServiceAccountEmail()); + } + + @Override + public boolean isValidServiceAccount(String jwt, String userIdentity, String... googleAudiences) { + GoogleIdTokenVerifier verifier = this.verifierGenerator.getVerifier(this.netHttpTransport, + this.jacksonFactory, googleAudiences); + try { + GoogleIdToken idToken = verifier.verify(jwt); + if (idToken != null) { + GoogleIdToken.Payload payload = idToken.getPayload(); + + String email = payload.getEmail(); + Boolean emailVerified = payload.getEmailVerified(); + + return (emailVerified && (email.equalsIgnoreCase(userIdentity))); + } else { + return false; + } + } catch (Exception e) { + this.log.error("Error when validating google id token", e); + return false; + } + } +} diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/JwtValidity.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/JwtValidity.java new file mode 100644 index 0000000000000000000000000000000000000000..282ad4c19c4aadae3306bb3c4db0b430aee6debe --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/JwtValidity.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.util; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class JwtValidity { + + String token; + long expiryTime; + + JwtValidity(String jwt, long expiryTime) { + this.token = jwt; + this.expiryTime = expiryTime; + } +} diff --git a/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/ServiceAccountJwtGcpClientImpl.java b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/ServiceAccountJwtGcpClientImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0bf66a94a46a2a85e3a98a5d799634364a96351f --- /dev/null +++ b/provider/notification-reference/src/main/java/org/opengroup/osdu/notification/provider/reference/util/ServiceAccountJwtGcpClientImpl.java @@ -0,0 +1,223 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.provider.reference.util; + +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.jackson.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.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.base.Strings; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +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.opengroup.osdu.core.common.model.http.AppException; +import org.opengroup.osdu.core.common.model.tenant.TenantInfo; +import org.opengroup.osdu.core.common.util.IServiceAccountJwtClient; +import org.opengroup.osdu.core.gcp.multitenancy.TenantFactory; + +public class ServiceAccountJwtGcpClientImpl implements IServiceAccountJwtClient { + + private AppProperties config; + 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 static final JsonFactory JSON_FACTORY = new JacksonFactory(); + static final String INVALID_INPUT = "Invalid inputs provided to getIdToken function"; + static final String INVALID_DATA_PARTITION = "Invalid data partition id"; + + private static ConcurrentHashMap<String, JwtValidity> jwtCache = new ConcurrentHashMap<>(); + private Iam iam; + + public ServiceAccountJwtGcpClientImpl(AppProperties config) { + if (config == null) { + throw new IllegalArgumentException("AppProperties is null when initializing jwt client."); + } else { + this.config = config; + } + } + + public String getIdToken(String dataPartitionId) { + String googleAudience = this.config.getGoogleAudiences(); + String hostName = this.config.getRegisterAPI(); + if (Strings.isNullOrEmpty(dataPartitionId) || Strings.isNullOrEmpty(googleAudience) + || Strings.isNullOrEmpty(hostName)) { + throw new AppException(HttpStatus.SC_BAD_REQUEST, + "data partition id, audiences or hostname are null", INVALID_INPUT); + } + try { + // Check if there is already a valid jwt + String key = dataPartitionId + googleAudience + hostName; + String jwt = checkAndGetJwtIfValid(key); + if (!Strings.isNullOrEmpty(jwt)) { + return jwt; + } + + TenantInfo tenantInfo = new TenantFactory().getTenantInfo(dataPartitionId); + if (tenantInfo == null) { + throw new AppException(HttpStatus.SC_BAD_REQUEST, "data partition id is invalid", + INVALID_DATA_PARTITION); + } + long currentTime = System.currentTimeMillis() / 1000; + long expiryTime = currentTime + 3600; + + // get signed JWT + Map<String, Object> signJwtPayload = this.getJwtCreationPayload(tenantInfo, googleAudience, + currentTime, expiryTime); + + SignJwtRequest signJwtRequest = new SignJwtRequest(); + signJwtRequest.setPayload(JSON_FACTORY.toString(signJwtPayload)); + + String serviceAccountName = String.format(SERVICE_ACCOUNT_NAME_FORMAT, + tenantInfo.getProjectId(), + tenantInfo.getServiceAccount()); + + Iam.Projects.ServiceAccounts.SignJwt signJwt = this.getIam(hostName).projects() + .serviceAccounts() + .signJwt(serviceAccountName, signJwtRequest); + SignJwtResponse signJwtResponse = signJwt.execute(); + String signedJwt = signJwtResponse.getSignedJwt(); + + // get 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")) { + throw new AppException(HttpStatus.SC_UNAUTHORIZED, + "User is not authorized to perform this operation.", + "Unauthorized to generate token"); + } + + String token = "Bearer " + jsonContent.get("id_token").getAsString(); + jwtCache.put(key, new JwtValidity(token, expiryTime)); + return token; + } + } catch (Exception e) { + throw new AppException(HttpStatus.SC_INTERNAL_SERVER_ERROR, + "Error happens when generating sauth token", "Error generating token", e); + } + + } + + Iam getIam(String hostName) throws GeneralSecurityException, IOException { + if (this.iam == null) { + HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport(); + + GoogleCredentials credential = GoogleCredentials.getApplicationDefault(); + if (credential.createScopedRequired()) { + List<String> scopes = new ArrayList<>(); + scopes.add(IamScopes.CLOUD_PLATFORM); + credential = credential.createScoped(scopes); + } + + this.iam = new Iam.Builder(httpTransport, JSON_FACTORY, + new HttpCredentialsAdapter(credential)) + .setApplicationName(hostName).build(); + } + + return this.iam; + } + + // THIS METHOD IS ONLY TO ENABLE UNIT TESTING + boolean reduceTenantExpiry(String dataPartitionId, String googleAudience, String hostName, + long keepDifference) { + JwtValidity jwtValidity = jwtCache.get(dataPartitionId + googleAudience + hostName); + if (jwtValidity == null) { + return false; + } + + long currentTime = System.currentTimeMillis() / 1000; + jwtValidity.expiryTime = currentTime + keepDifference; + return true; + } + + // THIS METHOD IS ONLY TO ENABLE UNIT TESTING + void clearCache() { + jwtCache.clear(); + } + + private String checkAndGetJwtIfValid(String key) { + JwtValidity jwtValidity = jwtCache.get(key); + if (jwtValidity == null) { + return null; + } + + // get current time + long currentTime = System.currentTimeMillis() / 1000; + + // If exipring in less than 5 minutes then need to renew the token + if (jwtValidity.expiryTime - 300 < currentTime) { + jwtCache.remove(key); + return null; + } + + return jwtValidity.token; + } + + + private Map<String, Object> getJwtCreationPayload(TenantInfo tenantInfo, String googleAudience, + long currentTime, long expiryTime) { + if (googleAudience.contains(",")) { + googleAudience = googleAudience.split(",")[0]; + } + Map<String, Object> payload = new HashMap<>(); + payload.put("target_audience", googleAudience); + payload.put("aud", JWT_AUDIENCE); + payload.put("exp", expiryTime); + payload.put("iat", currentTime); + payload.put("iss", tenantInfo.getServiceAccount()); + return payload; + } +} + + diff --git a/provider/notification-reference/src/main/resources/application.properties b/provider/notification-reference/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..f16a36215afb5b0e96810040ffcfe01e8eee60ca --- /dev/null +++ b/provider/notification-reference/src/main/resources/application.properties @@ -0,0 +1,33 @@ +# +# Copyright 2017-2020, 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. +# + +LOG_PREFIX=notification +logging.level.org.springframework.web=${LOG_LEVEL:DEBUG} +server.servlet.contextPath=/api/notification/v1 +app.expireTime=300 +app.maxCacheSize=10 +server.error.whitelabel.enabled=false + +authorize-api=${APP_ENTITLEMENTS} +register-api=${APP_REGISTER} +project-api=${APP_PROJECT} +expire-time=${APP_EXPIRE_TIME:300} +max-cache-size=${APP_MAX_CACHE_SIZE:10} + + +GOOGLE_AUDIENCES=${APP_AUDIENCES} +google-audiences=${APP_AUDIENCES} +partition-api=${PARTITION_API:http://localhost:8081/api/partition/v1} \ No newline at end of file diff --git a/provider/notification-reference/src/main/resources/logback.xml b/provider/notification-reference/src/main/resources/logback.xml new file mode 100644 index 0000000000000000000000000000000000000000..cbd32dfed70fc29a19a7876f0bb7deacc8daf3cb --- /dev/null +++ b/provider/notification-reference/src/main/resources/logback.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <include resource="org/springframework/boot/logging/logback/defaults.xml"/> + <property resource="application.properties"/> + <logger name="org.opengroup.osdu" level="${LOG_LEVEL}"/> + <springProfile name="local"> + <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%yellow([%thread]) %highlight(| %-5level |) %green(%d) %cyan(| %logger{15} |) + %highlight(%msg) %n + </pattern> + <charset>utf8</charset> + </encoder> + </appender> + <root level="info"> + <appender-ref ref="CONSOLE"/> + </root> + </springProfile> + + <springProfile name="!local"> + <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender"> + <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> + <layout class="ch.qos.logback.contrib.json.classic.JsonLayout"> + <timestampFormat>yyyy-MM-dd HH:mm:ss.SSS</timestampFormat> + <timestampFormatTimezoneId>Etc/UTC</timestampFormatTimezoneId> + <appendLineSeparator>true</appendLineSeparator> + + <jsonFormatter class="org.opengroup.osdu.core.gcp.logging.formatter.GoogleJsonFormatter"> + <prettyPrint>false</prettyPrint> + </jsonFormatter> + </layout> + </encoder> + </appender> + --> + + <root level="info"> + <appender-ref ref="stdout"/> + </root> + </springProfile> + +</configuration> \ No newline at end of file diff --git a/provider/notification-reference/src/main/test/java/org/opengroup/osdu/notification/util/GoogleServiceAccountValidatorGeneratorTest.java b/provider/notification-reference/src/main/test/java/org/opengroup/osdu/notification/util/GoogleServiceAccountValidatorGeneratorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..73f2011d0c27363e4e0da89635b9f487e277e98d --- /dev/null +++ b/provider/notification-reference/src/main/test/java/org/opengroup/osdu/notification/util/GoogleServiceAccountValidatorGeneratorTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.util; + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.jackson2.JacksonFactory; +import java.util.Collection; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.opengroup.osdu.notification.provider.reference.util.GoogleServiceAccountValidatorGenerator; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +public class GoogleServiceAccountValidatorGeneratorTest { + + private final NetHttpTransport netHttpTransport = new NetHttpTransport(); + private final JacksonFactory jacksonFactory = new JacksonFactory(); + private static final String AUDIENCE_1 = "aud1"; + private static final String AUDIENCE_2 = "aud2"; + + @InjectMocks + private GoogleServiceAccountValidatorGenerator sut; + + @Test + public void should_returnVerifierWithoutAudiences_when_noAudiencesProvided() { + GoogleIdTokenVerifier verifier = this.sut.getVerifier(netHttpTransport, jacksonFactory); + Assert.assertNull(verifier.getAudience()); + } + + @Test + public void should_returnVerifierWithAudiences_when_AudiencesProvided() { + GoogleIdTokenVerifier verifier = this.sut.getVerifier(netHttpTransport, jacksonFactory, + AUDIENCE_1, AUDIENCE_2); + Collection<String> audiences = verifier.getAudience(); + Assert.assertTrue(audiences.contains(AUDIENCE_1)); + Assert.assertTrue(audiences.contains(AUDIENCE_2)); + } +} diff --git a/provider/notification-reference/src/main/test/java/org/opengroup/osdu/notification/util/GoogleServiceAccountValidatorImplTests.java b/provider/notification-reference/src/main/test/java/org/opengroup/osdu/notification/util/GoogleServiceAccountValidatorImplTests.java new file mode 100644 index 0000000000000000000000000000000000000000..22f9497bfcfbdcbc5d92b5801d193f661f1d0557 --- /dev/null +++ b/provider/notification-reference/src/main/test/java/org/opengroup/osdu/notification/util/GoogleServiceAccountValidatorImplTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2021 Google LLC + * Copyright 2021 EPAM Systems, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.opengroup.osdu.notification.util; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; +import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; +import java.io.IOException; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; +import org.opengroup.osdu.notification.provider.reference.util.GoogleServiceAccountValidatorGenerator; +import org.opengroup.osdu.notification.provider.reference.util.GoogleServiceAccountValidatorImpl; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +public class GoogleServiceAccountValidatorImplTests { + + private static final String TEST_JWT = "testjwt"; + private static final String TEST_USER_IDENTITY = "testidentity"; + + @Mock + private JaxRsDpsLog log; + @Mock + private GoogleServiceAccountValidatorGenerator verifierGenerator; + @Mock + private GoogleIdTokenVerifier verifier; + @Mock + private GoogleIdToken idToken; + @Mock + private GoogleIdToken.Payload payload; + @InjectMocks + private GoogleServiceAccountValidatorImpl sut; + + @Test + public void should_returnTrue_when_tokenValidAndUserIdentityCorrect() throws Exception { + when(this.verifierGenerator.getVerifier(any(), any())).thenReturn(this.verifier); + when(this.verifier.verify(TEST_JWT)).thenReturn(this.idToken); + when(this.idToken.getPayload()).thenReturn(this.payload); + when(this.payload.getEmail()).thenReturn(TEST_USER_IDENTITY); + when(this.payload.getEmailVerified()).thenReturn(Boolean.TRUE); + Assert.assertTrue(this.sut.isValidServiceAccount(TEST_JWT, TEST_USER_IDENTITY)); + } + + @Test + public void should_returnFalse_when_tokenInvalid() throws Exception { + when(this.verifierGenerator.getVerifier(any(), any())).thenReturn(this.verifier); + when(this.verifier.verify(TEST_JWT)).thenReturn(null); + Assert.assertFalse(this.sut.isValidServiceAccount(TEST_JWT, TEST_USER_IDENTITY)); + } + + @Test + public void should_returnFalse_when_tokenValidAndUserIdentityIncorrect() throws Exception { + when(this.verifierGenerator.getVerifier(any(), any())).thenReturn(this.verifier); + when(this.verifier.verify(TEST_JWT)).thenReturn(this.idToken); + when(this.idToken.getPayload()).thenReturn(this.payload); + when(this.payload.getEmail()).thenReturn("wrongIdentity"); + when(this.payload.getEmailVerified()).thenReturn(Boolean.TRUE); + Assert.assertFalse(this.sut.isValidServiceAccount(TEST_JWT, TEST_USER_IDENTITY)); + } + + @Test + public void should_returnFalse_when_tokenValidAndUserIdentityCorrect_butEmailNotVerified() + throws Exception { + when(this.verifierGenerator.getVerifier(any(), any())).thenReturn(this.verifier); + when(this.verifier.verify(TEST_JWT)).thenReturn(this.idToken); + when(this.idToken.getPayload()).thenReturn(this.payload); + when(this.payload.getEmail()).thenReturn(TEST_USER_IDENTITY); + when(this.payload.getEmailVerified()).thenReturn(Boolean.FALSE); + Assert.assertFalse(this.sut.isValidServiceAccount(TEST_JWT, TEST_USER_IDENTITY)); + } + + @Test + public void should_logExceptionAndReturnFalse_when_tokenValidationThrowsException() + throws Exception { + when(this.verifierGenerator.getVerifier(any(), any())).thenReturn(this.verifier); + IOException e = new IOException("invalid token"); + when(this.verifier.verify(TEST_JWT)).thenThrow(e); + Assert.assertFalse(this.sut.isValidServiceAccount(TEST_JWT, TEST_USER_IDENTITY)); + verify(this.log, times(1)).error("Error when validating google id token", e); + } +} diff --git a/testing/notification-test-aws/pom.xml b/testing/notification-test-aws/pom.xml index c508099548475c07c59c0623051d8ae9fe44246e..c3336063beba95b562f28997ac649e3b8da0cfb6 100644 --- a/testing/notification-test-aws/pom.xml +++ b/testing/notification-test-aws/pom.xml @@ -21,13 +21,13 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification-testing</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-test-aws</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-test-aws</name> <description>AWS Integration tests for Notification Service</description> <packaging>jar</packaging> @@ -52,7 +52,7 @@ <dependency> <groupId>org.opengroup.osdu.notification</groupId> <artifactId>notification-test-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.opengroup.osdu</groupId> diff --git a/testing/notification-test-aws/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java b/testing/notification-test-aws/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java index f55ddb2c1550366a7db59d9f64b3417b8cdc9a46..15bce5ba911fe77c558a75f3dc4a29868d56d0f8 100644 --- a/testing/notification-test-aws/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java +++ b/testing/notification-test-aws/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java @@ -50,13 +50,6 @@ public class TestPubsubEndpointHMAC extends PubsubEndpointHMACTests { this.testUtils = null; } - @Test - @Override - public void should_return400_when_makingHttpRequestWithoutToken() throws Exception { - ClientResponse response = descriptor.run(getArg(), ""); - assertEquals(error(response.getEntity(String.class)), 403, response.getStatus()); - } - @Test @Override public void should_return401_when_noAccessOnCustomerTenant() throws Exception { diff --git a/testing/notification-test-azure/pom.xml b/testing/notification-test-azure/pom.xml index 28532c66c3047b77f5cc8dd7d2b63a63df830f6c..e206e2afba65d158c5af18936ceb106669981bce 100644 --- a/testing/notification-test-azure/pom.xml +++ b/testing/notification-test-azure/pom.xml @@ -21,13 +21,13 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification-testing</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-test-azure</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-test-azure</name> <description>Integration tests Azure for notification</description> <packaging>jar</packaging> @@ -42,7 +42,7 @@ <dependency> <groupId>org.opengroup.osdu.notification</groupId> <artifactId>notification-test-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> </dependency> <dependency> @@ -54,6 +54,10 @@ <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> </exclusion> + <exclusion> + <groupId>com.azure</groupId> + <artifactId>azure-storage-blob</artifactId> + </exclusion> <exclusion> <groupId>io.projectreactor.netty</groupId> <artifactId>reactor-netty</artifactId> diff --git a/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/StorageIntegrationDescriptor.java b/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/StorageIntegrationDescriptor.java new file mode 100644 index 0000000000000000000000000000000000000000..3dee5339b926db66dd40bd43d06b3e82400e3c11 --- /dev/null +++ b/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/StorageIntegrationDescriptor.java @@ -0,0 +1,86 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.api; + +import org.opengroup.osdu.notification.util.Config; +import org.opengroup.osdu.notification.util.RestDescriptor; +import org.opengroup.osdu.notification.util.TestUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class StorageIntegrationDescriptor extends RestDescriptor { + + protected static final String LEGAL_TAG = TestUtils.getOsduTenant() + "-test-tag"; + + @Override + public String getPath() { + return "records"; + } + + @Override + public String getHttpMethod() { + return "PUT"; + } + + @Override + public String getValidBody() { + return "[\n" + + " {\n" + + " \"data\":{\n" + + " \"Spuddate\":\"atspud\",\n" + + " \"UWI\":\"atuwi\",\n" + + " \"dlLatLongWGS84latitude\":\"latitude\",\n" + + " \"dlLatLongWGS84longitude\":\"longitude\"},\n" + + " \"version\":1591087431362345,\n" + + " \"kind\":\"opendes:at:wellbore:1.0.0\",\n" + + " \"acl\":{\n" + + " \"viewers\":[\n" + + " \"data.test1@opendes.contoso.com\"],\n" + + " \"owners\":[\n" + + " \"data.test1@opendes.contoso.com\"]},\n" + + " \"legal\":{\n" + + " \"legaltags\":[\n" + + " \"" + LEGAL_TAG + "\"],\n" + + " \"otherRelevantDataCountries\":[\n" + + " \"BR\"],\n" + + " \"status\":\"compliant\"},\n" + + " \"createUser\":\"integrationtest@opendes.iam.gserviceaccount.com\",\n" + + " \"createTime\":\"2020-06-01T18:32:52.054Z\",\n" + + " \"modifyUser\":\"integrationtest@opendes.iam.gserviceaccount.com\",\n" + + " \"modifyTime\":\"2020-06-02T08:43:51.553Z\"\n" + + " }\n" + + "]"; + } + + @Override + public Map<String, String> getOsduTenantHeaders() { + Map<String, String> headers = new HashMap<>(); + headers.put("data-partition-id", Config.Instance().OsduTenant); + headers.put("correlation-id", "storage-notification-it"); + return headers; + } + + // Customer Tenant headers is not required by the current Storage -Notification + // Integration Test scenario.It might be useful in upcoming test scenarios/cases + @Override + public Map<String, String> getCustomerTenantHeaders() { + Map<String, String> headers = new HashMap<>(); + headers.put("data-partition-id", Config.Instance().ClientTenant); + headers.put("correlation-id", "storage-notification-it"); + return headers; + } +} \ No newline at end of file diff --git a/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java b/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java index 3f429752e1bbd58ecb5b69bfb2c6e6fbab9bc9cf..501e113e44f7d097dd9661f4e5a7f01ba4641603 100644 --- a/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java +++ b/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java @@ -98,7 +98,7 @@ public class TestPubsubEndpointHMAC extends PubsubEndpointHMACTests { ClientResponse response = descriptor.run(this.getArg(), this.testUtils.getOpsToken()); Assert.assertEquals(this.error(response.getStatus() == 204 ? "" : (String)response.getEntity(String.class)), (long)this.expectedOkResponseCode(), (long)response.getStatus()); Assert.assertEquals("GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH", response.getHeaders().getFirst("Access-Control-Allow-Methods")); - Assert.assertEquals("origin, content-type, accept, authorization, data-partition-id, correlation-id, appkey", response.getHeaders().getFirst("Access-Control-Allow-Headers")); + Assert.assertEquals("access-control-allow-origin, origin, content-type, accept, authorization, data-partition-id, correlation-id, appkey", response.getHeaders().getFirst("Access-Control-Allow-Headers")); Assert.assertEquals("*", response.getHeaders().getFirst("Access-Control-Allow-Origin")); Assert.assertEquals("true", response.getHeaders().getFirst("Access-Control-Allow-Credentials")); Assert.assertEquals("DENY", response.getHeaders().getFirst("X-Frame-Options")); diff --git a/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/TestStorageIntegration.java b/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/TestStorageIntegration.java new file mode 100644 index 0000000000000000000000000000000000000000..e25791aa146f8aa61a61c8213d24bed27bf89b58 --- /dev/null +++ b/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/api/TestStorageIntegration.java @@ -0,0 +1,241 @@ +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.opengroup.osdu.notification.api; + +import com.sun.jersey.api.client.ClientResponse; +import org.apache.catalina.connector.Response; +import org.apache.commons.lang3.time.StopWatch; +import org.asynchttpclient.util.Assertions; +import org.junit.*; +import org.opengroup.osdu.core.common.model.http.DpsHeaders; +import org.opengroup.osdu.core.common.model.notification.HmacSecret; +import org.opengroup.osdu.core.common.model.notification.Subscription; +import org.opengroup.osdu.core.common.model.notification.SubscriptionInfo; +import org.opengroup.osdu.core.common.notification.ISubscriptionService; +import org.opengroup.osdu.core.common.notification.SubscriptionAPIConfig; +import org.opengroup.osdu.core.common.notification.SubscriptionException; +import org.opengroup.osdu.core.common.notification.SubscriptionFactory; +import org.opengroup.osdu.notification.util.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class TestStorageIntegration extends BaseTestTemplate { + + private String subscriptionId = null; + private String notificationId = null; + private final String ackSubscriptionId = "AckSubscription"; + private final String ackNotificationId = "testingAcknowledgement"; + private ISubscriptionService subscriptionService; + private static SubscriptionFactory factory; + protected static final String LEGAL_TAG = TestUtils.getOsduTenant() + "-test-tag"; + + @BeforeClass + public static void classSetup() throws Exception { + descriptor = new StorageIntegrationDescriptor(); + //Configure Register Service Client Library + SubscriptionAPIConfig config = SubscriptionAPIConfig.builder().rootUrl(Config.Instance().RegisterServicePath).build(); + factory = new SubscriptionFactory(config); + } + + @AfterClass + public static void classTearDown() throws Exception { + } + + @Before + @Override + public void setup() throws Exception { + this.testUtils = new AzureTestUtils(); + } + + @Override + protected void deleteResource() throws Exception { + subscriptionService.delete(subscriptionId); + } + + @After + @Override + public void tearDown() throws Exception { + this.testUtils = null; + } + + @Override + protected void createResource() throws Exception { + createResourceInPartition(TestUtils.getOsduTenant()); + } + + private void createResourceInPartition(String partitionId) throws Exception { + Map<String, String> headers = new HashMap<>(); + headers.put(DpsHeaders.DATA_PARTITION_ID, partitionId); + headers.put(DpsHeaders.AUTHORIZATION, testUtils.getOpsToken()); + DpsHeaders dpsHeaders = DpsHeaders.createFromMap(headers); + subscriptionService = factory.create(dpsHeaders); + + // Create a new subscription + Subscription subscription = new Subscription(); + subscription.setName("storage-integration-test-hmac"); + subscription.setDescription("Subscription created for Storage Integration Tests"); + subscription.setTopic(Config.Instance().Topic); + subscription.setPushEndpoint(Config.Instance().HMACPushUrl); + HmacSecret secret = new HmacSecret(); + secret.setValue(Config.Instance().hmacSecretValue); + subscription.setSecret(secret); + try { + Subscription subscriptionCreated = subscriptionService.create(subscription); + notificationId = subscriptionCreated.getNotificationId(); + subscriptionId = subscriptionCreated.getId(); + Config.Instance().NotificationId = notificationId; + } catch (SubscriptionException e) { + System.out.println("Subscription exception inner response : " + e.getHttpResponse()); + throw e; + } + } + + private void deleteAckSubscription() throws SubscriptionException { + try { + subscriptionService.delete(ackSubscriptionId); + } catch (SubscriptionException e) { + if (e.getHttpResponse().getResponseCode() == Response.SC_NOT_FOUND) { + System.out.println("Test Ack Subscription Not Found for deletion."); + return; + } + Assert.fail("Unable to delete Test Ack Subscription. Deletion Failed." + e); + throw e; + } + } + + @Override + protected String getArg() { + return null; + } + + @Override + protected String getInvalidArg() { + return null; + } + + @Override + protected int expectedOkResponseCode() { + return 200; + } + + @Test + @Override + public void should_return20XResponseCode_when_makingValidHttpsRequest() throws Exception { + try { + // Creates an actual subscription with a test endpoint i.e REGISTER_CUSTOM_PUSH_URL_HMAC + this.createResource(); + // Delay to get the above subscription registered by Notification Service + TimeUnit.SECONDS.sleep(60); + + // Delete the Test Ack subscription if there exists any as a part of cleanup. + // The Test Ack Subscription gets created if the the test endpoint of actual subscription + // created above receives notification from Notification Service. + this.deleteAckSubscription(); + + // Create legal tag used in storage record + LegalTagUtils.create(LEGAL_TAG, this.testUtils.getAdminToken()); + + // Insert a storage Record.This will send a notification to the event grid/service bus topic + // subscriptions.From there it goes to the Notification Service.Finally from there it goes to the + // test endpoint i.e REGISTER_CUSTOM_PUSH_URL_HMAC which creates the Test ack Subscription. + String URL = Config.Instance().StorageServicePath; + ClientResponse response = descriptor.run(URL, "", this.testUtils.getAdminToken()); + assertEquals(error(response.getEntity(String.class)), 201, response.getStatus()); + + // Verification of Test Ack Subscription creation from the above process. + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + // Retrieval of test ack subscription from Cosmos-DB. + long retryCount = Long.parseLong(Config.Instance().RetryCount); + long timeOut = Long.parseLong(Config.Instance().TimeOutSeconds); + SubscriptionInfo ackSubscription = subscriptionService.get(ackSubscriptionId); + while (ackSubscription == null && retryCount > 0) { + TimeUnit.SECONDS.sleep(timeOut); + ackSubscription = subscriptionService.get(ackSubscriptionId); + retryCount--; + } + Assertions.assertNotNull(ackSubscription, "Unable to retrieve test ack subscription. Elapsed time in minutes : " + (stopWatch.getTime(TimeUnit.MINUTES))); + if (ackSubscription != null) { + assertEquals("Unexpected Test Ack Subscription.", ackNotificationId, ackSubscription.getNotificationId()); + } + stopWatch.stop(); + } catch (Exception e) { + fail("An exception occurred :" + e); + } finally { + // Delete Actual Subscription + this.deleteResource(); + // Deletion of Test Ack Subscription + this.deleteAckSubscription(); + // Delete legal tag + LegalTagUtils.delete(LEGAL_TAG, this.testUtils.getAdminToken()); + + } + } + + /* Keeping the No-op test cases implementation as ignored as the Storage-Notification Integration + Test scenario does not involves them. */ + @Test + @Override + @Ignore + public void should_return400_when_makingHttpRequestWithoutToken() throws Exception { + } + + @Test + @Override + @Ignore + public void should_return307_when_makingHttpRequest() throws Exception { + } + + @Test + @Override + @Ignore + public void should_return401_when_accessingWithNoAccessCredentials() throws Exception { + } + + @Test + @Override + @Ignore + public void should_return401_when_noAccessOnCustomerTenant() throws Exception { + } + + @Test + @Override + @Ignore + public void should_return401_when_accessingWithEditorCredentials() throws Exception { + } + + @Test + @Override + @Ignore + public void should_return401_when_accessingWithAdminCredentials() throws Exception { + } + + @Test + @Override + @Ignore + public void should_return20X_when_usingCredentialsWithOpsPermission() throws Exception { + } + + @Test + @Override + @Ignore + public void should_returnOk_when_makingHttpOptionsRequest() throws Exception { + } +} \ No newline at end of file diff --git a/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/util/LegalTagUtils.java b/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/util/LegalTagUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c9f1eb14acdb95412a073c6b4316b4f682a07990 --- /dev/null +++ b/testing/notification-test-azure/src/test/java/org/opengroup/osdu/notification/util/LegalTagUtils.java @@ -0,0 +1,75 @@ +// 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.opengroup.osdu.notification.util; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.sun.jersey.api.client.ClientResponse; +import org.apache.http.HttpStatus; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class LegalTagUtils { + public static ClientResponse create(String legalTagName, String token) throws Exception { + return create("US", legalTagName, "2099-01-25", "Public Domain Data", token); + } + + protected static ClientResponse create(String countryOfOrigin, String name, String expDate, String dataType, String token) + throws Exception { + String body = getBody(countryOfOrigin, name, expDate, dataType); + Map<String, String> headers = new HashMap<>(); + headers.put("Data-Partition-Id", TestUtils.getOsduTenant()); + ClientResponse response = TestUtils.send(getLegalUrl(), "legaltags", "POST", token, body, "", headers, false); + assertEquals(HttpStatus.SC_CREATED, response.getStatus()); + Thread.sleep(100); + return response; + } + + public static ClientResponse delete(String legalTagName, String token) throws Exception { + Map<String, String> headers = new HashMap<>(); + headers.put("Data-Partition-Id", TestUtils.getOsduTenant()); + return TestUtils.send(getLegalUrl(), "legaltags/" + legalTagName, "DELETE", token, "", "", headers, false); + } + + protected static String getLegalUrl() { + return Config.Instance().LegalServicePath; + } + + protected static String getBody(String countryOfOrigin, String name, String expDate, String dataType) { + + JsonArray coo = new JsonArray(); + coo.add(countryOfOrigin); + + JsonObject properties = new JsonObject(); + properties.add("countryOfOrigin", coo); + properties.addProperty("contractId", "A1234"); + properties.addProperty("expirationDate", expDate); + properties.addProperty("dataType", dataType); + properties.addProperty("originator", "MyCompany"); + properties.addProperty("securityClassification", "Public"); + properties.addProperty("exportClassification", "EAR99"); + properties.addProperty("personalData", "No Personal Data"); + + JsonObject tag = new JsonObject(); + tag.addProperty("name", name); + tag.addProperty("description", "test for " + name); + tag.add("properties", properties); + + return tag.toString(); + } +} diff --git a/testing/notification-test-core/pom.xml b/testing/notification-test-core/pom.xml index 9a12fe414bdacee481914f218b8a6cc5f821f682..02b337ba0c84b7c175fa568cb40fe05ab4697eb9 100644 --- a/testing/notification-test-core/pom.xml +++ b/testing/notification-test-core/pom.xml @@ -21,13 +21,13 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification-testing</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <groupId>org.opengroup.osdu.notification</groupId> <artifactId>notification-test-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-test-core</name> <description>Integration tests core for notification</description> <packaging>jar</packaging> @@ -113,7 +113,7 @@ <dependency> <groupId>com.google.auth</groupId> <artifactId>google-auth-library-oauth2-http</artifactId> - <version>0.9.0</version> + <version>0.11.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> diff --git a/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/BaseTestTemplate.java b/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/BaseTestTemplate.java index 8792991c580a08210a61326e527cbfca5c61459a..01a0be5de9ef90263eb40f6efd729e3919b72ddb 100644 --- a/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/BaseTestTemplate.java +++ b/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/BaseTestTemplate.java @@ -89,7 +89,7 @@ public abstract class BaseTestTemplate extends TestBase { assertEquals(error(response.getStatus() == 204 ? "" : response.getEntity(String.class)), expectedOkResponseCode(), response.getStatus()); assertEquals("GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH", response.getHeaders().getFirst("Access-Control-Allow-Methods")); - assertEquals("origin, content-type, accept, authorization, data-partition-id, correlation-id, appkey", response.getHeaders().getFirst("Access-Control-Allow-Headers")); + assertEquals("access-control-allow-origin, origin, content-type, accept, authorization, data-partition-id, correlation-id, appkey", response.getHeaders().getFirst("Access-Control-Allow-Headers")); assertEquals("*", response.getHeaders().getFirst("Access-Control-Allow-Origin")); assertEquals("true", response.getHeaders().getFirst("Access-Control-Allow-Credentials")); assertEquals("DENY", response.getHeaders().getFirst("X-Frame-Options")); diff --git a/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/Config.java b/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/Config.java index 0d126cd2cd08d10993df45dd83c61bc62984ea07..8f7655b27ddf731230131e91fedad90b67809763 100644 --- a/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/Config.java +++ b/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/Config.java @@ -24,55 +24,61 @@ public class Config { public String GSAPushUrl; public String HMACPushUrl; public String RegisterServicePath; + public String StorageServicePath; + public String LegalServicePath; public String Topic; public String hmacSecretValue; public String NotificationId; public String DE_OPS_TESTER = System.getProperty("DE_OPS_TESTER", System.getenv("DE_OPS_TESTER")); - + public String RetryCount; + public String TimeOutSeconds; private static Config config = new Config(); public static Config Instance() { String env = getEnvironment(); - config.ClientTenant = getEnvironmentVariableOrDefaultValue("CLIENT_TENANT","nonexistenttenant"); - config.IntegrationAudience = getEnvironmentVariableOrDefaultValue("INTEGRATION_TEST_AUDIENCE","245464679631-ktfdfpl147m1mjpbutl00b3cmffissgq.apps.googleusercontent.com"); - config.OsduTenant = getEnvironmentVariableOrDefaultValue("OSDU_TENANT","opendes"); - config.Topic = getEnvironmentVariableOrDefaultValue("TOPIC_ID","records-changed"); - + config.ClientTenant = getEnvironmentVariableOrDefaultValue("CLIENT_TENANT", "nonexistenttenant"); + config.IntegrationAudience = getEnvironmentVariableOrDefaultValue("INTEGRATION_TEST_AUDIENCE", "245464679631-ktfdfpl147m1mjpbutl00b3cmffissgq.apps.googleusercontent.com"); + config.OsduTenant = getEnvironmentVariableOrDefaultValue("OSDU_TENANT", "opendes"); + config.Topic = getEnvironmentVariableOrDefaultValue("TOPIC_ID", "records-changed"); + config.TimeOutSeconds = getEnvironmentVariableOrDefaultValue("TIME_OUT_SECONDS", "60"); + config.RetryCount = getEnvironmentVariableOrDefaultValue("RETRY_COUNT", "3"); config.hmacSecretValue = System.getProperty("HMAC_SECRET", System.getenv("HMAC_SECRET")); if (env.equalsIgnoreCase("LOCAL")) { //make sure to run register service on a different port. You can also choose to point to Register service that is running in cloud String registerUrl = "http://localhost:8081/api/register/v1"; - //must have notification and register services running on different ports config.HostUrl = "http://localhost:8080/"; - config.GSAPushUrl = registerUrl+"/test/gsa-challenge/"; - config.HMACPushUrl = registerUrl+"/test/challenge/"; + config.GSAPushUrl = registerUrl + "/test/gsa-challenge/"; + config.HMACPushUrl = registerUrl + "/test/challenge/"; config.RegisterServicePath = registerUrl; + config.StorageServicePath = "http://localhost:8085/api/storage/v2/"; + config.LegalServicePath = "http://localhost:8087/api/legal/v1/"; + } else if (env.equalsIgnoreCase("DEV") || isGke() || env.equalsIgnoreCase("CLOUD")) { String registerUrl = System.getProperty("REGISTER_BASE_URL", System.getenv("REGISTER_BASE_URL")); config.HostUrl = System.getProperty("NOTIFICATION_BASE_URL", System.getenv("NOTIFICATION_BASE_URL")); - config.GSAPushUrl = registerUrl+"/test/gsa-challenge/"; + config.GSAPushUrl = registerUrl + "/test/gsa-challenge/"; //Adding this so CPs can point to custom HMAC push endpoints - config.HMACPushUrl = getEnvironmentVariableOrDefaultValue("REGISTER_CUSTOM_PUSH_URL_HMAC",registerUrl+"/test/challenge/"); + config.HMACPushUrl = getEnvironmentVariableOrDefaultValue("REGISTER_CUSTOM_PUSH_URL_HMAC", registerUrl + "/test/challenge/"); //Adding a new variable NOTIFICATION_REGISTER_BASE_URL since REGISTER_BASE_URL is used by Register integration tests which needs a trailing \ - String regUrl= getEnvironmentVariable("NOTIFICATION_REGISTER_BASE_URL"); - if(regUrl==null) - { - config.RegisterServicePath = registerUrl; - } - else - { - config.RegisterServicePath = regUrl+"/api/register/v1"; - } + String regUrl = getEnvironmentVariable("NOTIFICATION_REGISTER_BASE_URL"); + config.StorageServicePath = getEnvironmentVariable("STORAGE_HOST"); + config.LegalServicePath = System.getProperty("LEGAL_URL", System.getenv("LEGAL_URL")); + if (regUrl == null) { + config.RegisterServicePath = registerUrl; + } else { + config.RegisterServicePath = regUrl + "/api/register/v1"; + } - }else + } else throw new RuntimeException("$ENVIRONMENT environment variable not provided"); - System.out.println("HostUrl="+config.HostUrl); - System.out.println("config.Topic="+ config.Topic); - System.out.println("config.HMACPushUrl="+ config.HMACPushUrl); - System.out.println("config.RegisterServicePath="+ config.RegisterServicePath); + System.out.println("HostUrl=" + config.HostUrl); + System.out.println("config.Topic=" + config.Topic); + System.out.println("config.HMACPushUrl=" + config.HMACPushUrl); + System.out.println("config.RegisterServicePath=" + config.RegisterServicePath); + System.out.println("config.StorageServicePath=" + config.StorageServicePath); return config; } diff --git a/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/RestDescriptor.java b/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/RestDescriptor.java index 38690fc885965e34570151a04883230632f48c86..08bab7e551b06c5f172aead358a159d99801257b 100644 --- a/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/RestDescriptor.java +++ b/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/RestDescriptor.java @@ -22,34 +22,51 @@ import java.util.Map; public abstract class RestDescriptor { - public RestDescriptor() { - } - - private String arg = ""; - public String arg(){ - return arg; - } - public abstract String getPath(); - public abstract String getHttpMethod(); - public abstract String getValidBody(); - public abstract Map<String,String> getOsduTenantHeaders(); - public abstract Map<String,String> getCustomerTenantHeaders(); - public String getQuery() { return ""; } - - public ClientResponse runHttp(String arg, String token) throws Exception{ - this.arg = arg; - return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getOsduTenantHeaders(),true); - } - public ClientResponse run(String arg, String token) throws Exception{ - this.arg = arg; - return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getOsduTenantHeaders(),false); - } - public ClientResponse runOnCustomerTenant(String arg, String token) throws Exception{ - this.arg = arg; - return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getCustomerTenantHeaders(), false); - } - public ClientResponse runOptions(String arg, String token) throws Exception{ - this.arg = arg; - return TestUtils.send(getPath(), "OPTIONS", token, "", "", getOsduTenantHeaders(),false); - } + public RestDescriptor() { + } + + private String arg = ""; + + public String arg() { + return arg; + } + + public abstract String getPath(); + + public abstract String getHttpMethod(); + + public abstract String getValidBody(); + + public abstract Map<String, String> getOsduTenantHeaders(); + + public abstract Map<String, String> getCustomerTenantHeaders(); + + public String getQuery() { + return ""; + } + + public ClientResponse runHttp(String arg, String token) throws Exception { + this.arg = arg; + return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getOsduTenantHeaders(), true); + } + + public ClientResponse run(String arg, String token) throws Exception { + this.arg = arg; + return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getOsduTenantHeaders(), false); + } + + public ClientResponse runOnCustomerTenant(String arg, String token) throws Exception { + this.arg = arg; + return TestUtils.send(getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getCustomerTenantHeaders(), false); + } + + public ClientResponse runOptions(String arg, String token) throws Exception { + this.arg = arg; + return TestUtils.send(getPath(), "OPTIONS", token, "", "", getOsduTenantHeaders(), false); + } + + public ClientResponse run(String url, String arg, String token) throws Exception { + this.arg = arg; + return TestUtils.send(url, getPath(), getHttpMethod(), token, getValidBody(), getQuery(), getOsduTenantHeaders(), false); + } } \ No newline at end of file diff --git a/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/TestUtils.java b/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/TestUtils.java index 393089845fdad198817e00950eb9e7300dd7ff84..17d5d90b5ded8ad2a87658a3aebde2022ae8b1d0 100644 --- a/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/TestUtils.java +++ b/testing/notification-test-core/src/main/java/org/opengroup/osdu/notification/util/TestUtils.java @@ -35,46 +35,56 @@ import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; public abstract class TestUtils { - protected String serviceAccountFile; - protected static String opsToken = null; - protected static String adminToken = null; - protected static String editorToken = null; - protected static String noAccessToken = null; - - public static String getApiPath(String api, boolean enforceHttp) throws Exception { - String baseUrl = Config.Instance().HostUrl; - if(enforceHttp) - baseUrl = baseUrl.replaceFirst("https", "http"); - URL mergedURL = new URL(baseUrl + api); - return mergedURL.toString(); - } - - public static String getOsduTenant(){ - return Config.Instance().OsduTenant; - } - - public static String getCustomerTenant(){ - return Config.Instance().ClientTenant; - } - - public abstract String getOpsToken() throws Exception; - public abstract String getAdminToken() throws Exception; - public abstract String getEditorToken() throws Exception; - public abstract String getNoAccessToken() throws Exception; + protected String serviceAccountFile; + protected static String opsToken = null; + protected static String adminToken = null; + protected static String editorToken = null; + protected static String noAccessToken = null; + + public static String getApiPath(String api, boolean enforceHttp) throws Exception { + String baseUrl = Config.Instance().HostUrl; + if (enforceHttp) + baseUrl = baseUrl.replaceFirst("https", "http"); + URL mergedURL = new URL(baseUrl + api); + return mergedURL.toString(); + } + + public static String getApiPath(String baseUrl, String api, boolean enforceHttp) throws Exception { + if (enforceHttp) + baseUrl = baseUrl.replaceFirst("https", "http"); + URL mergedURL = new URL(baseUrl + api); + return mergedURL.toString(); + } + + public static String getOsduTenant() { + return Config.Instance().OsduTenant; + } + + public static String getCustomerTenant() { + return Config.Instance().ClientTenant; + } + + public abstract String getOpsToken() throws Exception; + + public abstract String getAdminToken() throws Exception; + + public abstract String getEditorToken() throws Exception; + + public abstract String getNoAccessToken() throws Exception; public static ClientResponse send(String path, String httpMethod, String token, String requestBody, String query, - Map<String,String> headers, boolean enforceHttp) + Map<String, String> headers, boolean enforceHttp) throws Exception { Client client = getClient(); - client.setConnectTimeout(15000); - client.setReadTimeout(15000); + client.setConnectTimeout(50000); + client.setReadTimeout(50000); client.setFollowRedirects(false); String url = getApiPath(path + query, enforceHttp); System.out.println(url); - System.out.println(httpMethod); - System.out.println(requestBody); - System.out.println(headers); + System.out.println(httpMethod); + System.out.println(requestBody); + System.out.println(headers); WebResource webResource = client.resource(url); final WebResource.Builder builder = webResource.type(MediaType.APPLICATION_JSON) @@ -85,47 +95,72 @@ public abstract class TestUtils { return response; } - @SuppressWarnings("unchecked") - public <T> T getResult(ClientResponse response, int exepectedStatus, Class<T> classOfT) { - String json = response.getEntity(String.class); - - assertEquals(exepectedStatus, response.getStatus()); - if (exepectedStatus == 204) { - return null; - } - - assertEquals(MediaType.APPLICATION_JSON, response.getType().toString()); - if (classOfT == String.class) { - return (T) json; - } - - Gson gson = new Gson(); - return gson.fromJson(json, classOfT); - } - - public static Client getClient() { - TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - - @Override - public void checkClientTrusted(X509Certificate[] certs, String authType) { - } - - @Override - public void checkServerTrusted(X509Certificate[] certs, String authType) { - } - } }; - - try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(null, trustAllCerts, new SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - } catch (Exception e) { - } - - return Client.create(); - } + public static ClientResponse send(String url, String path, String httpMethod, String token, String requestBody, + String query, Map<String, String> headers, boolean enforceHttp) + throws Exception { + + Client client = getClient(); + client.setConnectTimeout(50000); + client.setReadTimeout(50000); + client.setFollowRedirects(false); + String URL = getApiPath(url, path + query, enforceHttp); + System.out.println(url + path); + System.out.println(httpMethod); + System.out.println(requestBody); + System.out.println(headers); + WebResource webResource = client.resource(URL); + final WebResource.Builder builder = webResource.type(MediaType.APPLICATION_JSON); + if (!token.isEmpty()) { + builder.header("Authorization", token); + } + headers.forEach((k, v) -> builder.header(k, v)); + ClientResponse response = builder.method(httpMethod, ClientResponse.class, requestBody); + + return response; + + } + + @SuppressWarnings("unchecked") + public <T> T getResult(ClientResponse response, int exepectedStatus, Class<T> classOfT) { + String json = response.getEntity(String.class); + + assertEquals(exepectedStatus, response.getStatus()); + if (exepectedStatus == 204) { + return null; + } + + assertEquals(MediaType.APPLICATION_JSON, response.getType().toString()); + if (classOfT == String.class) { + return (T) json; + } + + Gson gson = new Gson(); + return gson.fromJson(json, classOfT); + } + + public static Client getClient() { + TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + }}; + + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } catch (Exception e) { + } + + return Client.create(); + } } \ No newline at end of file diff --git a/testing/notification-test-gcp/pom.xml b/testing/notification-test-gcp/pom.xml index 9ae43b5892daff41afe2f7b3e42809f2945e78ac..e22afb311f41d015a0f9a30f1cc19660ec8b7ef6 100644 --- a/testing/notification-test-gcp/pom.xml +++ b/testing/notification-test-gcp/pom.xml @@ -21,12 +21,12 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification-testing</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>notification-test-gcp</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-test-gcp</name> <description>Integration tests GCP for notification</description> <packaging>jar</packaging> @@ -41,7 +41,7 @@ <dependency> <groupId>org.opengroup.osdu.notification</groupId> <artifactId>notification-test-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.opengroup.osdu</groupId> diff --git a/testing/notification-test-gcp/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointGSA.java b/testing/notification-test-gcp/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointGSA.java index 0aa5c1c3cfa70e87ecc0ea8d0bf4d306192b673e..a743915e56c34ee0919d4213dbf74727a9b5b0b1 100644 --- a/testing/notification-test-gcp/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointGSA.java +++ b/testing/notification-test-gcp/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointGSA.java @@ -1,36 +1,72 @@ package org.opengroup.osdu.notification.api; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.sun.jersey.api.client.ClientResponse; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; -import org.opengroup.osdu.notification.util.Config; +import org.junit.Test; import org.opengroup.osdu.notification.util.GCPTestUtils; -import org.opengroup.osdu.notification.util.RestDescriptor; public class TestPubsubEndpointGSA extends PubsubEndpointGSATests { - private static final GCPTestUtils gcpTestUtils = new GCPTestUtils(); + private static final GCPTestUtils gcpTestUtils = new GCPTestUtils(); - @BeforeClass - public static void classSetup() throws Exception { - PubsubEndpointGSATests.classSetup(gcpTestUtils.getOpsToken()); - } + @BeforeClass + public static void classSetup() throws Exception { + PubsubEndpointGSATests.classSetup(gcpTestUtils.getOpsToken()); + } - @AfterClass - public static void classTearDown() throws Exception { - } + @AfterClass + public static void classTearDown() throws Exception { + } - @Before - @Override - public void setup() throws Exception { - this.testUtils = new GCPTestUtils(); - } + @Before + @Override + public void setup() throws Exception { + this.testUtils = new GCPTestUtils(); + } + + @After + @Override + public void tearDown() throws Exception { + this.testUtils = null; + } + + @Override + @Test + public void should_return20X_when_usingCredentialsWithOpsPermission() throws Exception { + createResource(); + + try { + ClientResponse response = descriptor.run(getArg(), testUtils.getOpsToken()); - @After - @Override - public void tearDown() throws Exception { - this.testUtils = null; + assertEquals(error(response.getStatus() == 204 ? "" : response.getEntity(String.class)), + expectedOkResponseCode(), response.getStatus()); + assertEquals("GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH", + response.getHeaders().getFirst("Access-Control-Allow-Methods")); + assertEquals( + "access-control-allow-origin, origin, content-type, accept, authorization, data-partition-id, correlation-id, appkey", + response.getHeaders().getFirst("Access-Control-Allow-Headers")); + assertEquals("*", response.getHeaders().getFirst("Access-Control-Allow-Origin")); + assertEquals("true", response.getHeaders().getFirst("Access-Control-Allow-Credentials")); + assertEquals("DENY", response.getHeaders().getFirst("X-Frame-Options")); + assertEquals("1; mode=block", response.getHeaders().getFirst("X-XSS-Protection")); + assertEquals("nosniff", response.getHeaders().getFirst("X-Content-Type-Options")); + assertEquals("no-cache, no-store, must-revalidate", + response.getHeaders().getFirst("Cache-Control")); + assertEquals("default-src 'self'", response.getHeaders().getFirst("Content-Security-Policy")); + assertTrue(response.getHeaders().get("Strict-Transport-Security").get(0) + .contains("max-age=31536000")); + assertTrue(response.getHeaders().get("Strict-Transport-Security").get(0) + .contains("includeSubDomains")); + assertEquals("0", response.getHeaders().getFirst("Expires")); + } finally { + deleteResource(); } + } } \ No newline at end of file diff --git a/testing/notification-test-gcp/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java b/testing/notification-test-gcp/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java index af7b1fba71692e4e40bb78ba9ef5679eaea2af6e..5ba744d1eb3c15eebab7d0c71bdcb0a1a02d0b00 100644 --- a/testing/notification-test-gcp/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java +++ b/testing/notification-test-gcp/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java @@ -1,34 +1,69 @@ package org.opengroup.osdu.notification.api; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.sun.jersey.api.client.ClientResponse; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; -import org.opengroup.osdu.notification.util.Config; +import org.junit.Test; import org.opengroup.osdu.notification.util.GCPTestUtils; -import org.opengroup.osdu.notification.util.RestDescriptor; -import org.opengroup.osdu.notification.util.TestUtils; public class TestPubsubEndpointHMAC extends PubsubEndpointHMACTests { - @BeforeClass - public static void classSetup() throws Exception { - PubsubEndpointHMACTests.classSetup(); - } + @BeforeClass + public static void classSetup() throws Exception { + PubsubEndpointHMACTests.classSetup(); + } - @AfterClass - public static void classTearDown() throws Exception { - } + @AfterClass + public static void classTearDown() throws Exception { + } - @Before - @Override - public void setup() throws Exception { - this.testUtils = new GCPTestUtils(); - } + @Before + @Override + public void setup() throws Exception { + this.testUtils = new GCPTestUtils(); + } + + @After + @Override + public void tearDown() throws Exception { + this.testUtils = null; + } + + @Override + @Test + public void should_return20X_when_usingCredentialsWithOpsPermission() throws Exception { + createResource(); + + try { + ClientResponse response = descriptor.run(getArg(), testUtils.getOpsToken()); - @After - @Override - public void tearDown() throws Exception { - this.testUtils = null; + assertEquals(error(response.getStatus() == 204 ? "" : response.getEntity(String.class)), + expectedOkResponseCode(), response.getStatus()); + assertEquals("GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH", + response.getHeaders().getFirst("Access-Control-Allow-Methods")); + assertEquals( + "access-control-allow-origin, origin, content-type, accept, authorization, data-partition-id, correlation-id, appkey", + response.getHeaders().getFirst("Access-Control-Allow-Headers")); + assertEquals("*", response.getHeaders().getFirst("Access-Control-Allow-Origin")); + assertEquals("true", response.getHeaders().getFirst("Access-Control-Allow-Credentials")); + assertEquals("DENY", response.getHeaders().getFirst("X-Frame-Options")); + assertEquals("1; mode=block", response.getHeaders().getFirst("X-XSS-Protection")); + assertEquals("nosniff", response.getHeaders().getFirst("X-Content-Type-Options")); + assertEquals("no-cache, no-store, must-revalidate", + response.getHeaders().getFirst("Cache-Control")); + assertEquals("default-src 'self'", response.getHeaders().getFirst("Content-Security-Policy")); + assertTrue(response.getHeaders().get("Strict-Transport-Security").get(0) + .contains("max-age=31536000")); + assertTrue(response.getHeaders().get("Strict-Transport-Security").get(0) + .contains("includeSubDomains")); + assertEquals("0", response.getHeaders().getFirst("Expires")); + } finally { + deleteResource(); } + } } \ No newline at end of file diff --git a/testing/notification-test-ibm/pom.xml b/testing/notification-test-ibm/pom.xml index b374c02678e26207220d304ee42d872dc1710bd9..9f52e4f684bcf892861219a9c6d0ab5277caf0e0 100644 --- a/testing/notification-test-ibm/pom.xml +++ b/testing/notification-test-ibm/pom.xml @@ -18,13 +18,13 @@ <parent> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification-testing</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> <groupId>org.opengroup.osdu</groupId> <artifactId>notification-test-ibm</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <name>notification-test-ibm</name> <description>Integration tests IBM for notification</description> <packaging>jar</packaging> @@ -45,7 +45,7 @@ <dependency> <groupId>org.opengroup.osdu.notification</groupId> <artifactId>notification-test-core</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.opengroup.osdu</groupId> diff --git a/testing/notification-test-ibm/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java b/testing/notification-test-ibm/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java index 9a1f59c5a4d8f9d51cfb3175be4ddbc103554d5b..e01b1c888f7f1f79f9707246c3ebb3ed26d56380 100644 --- a/testing/notification-test-ibm/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java +++ b/testing/notification-test-ibm/src/test/java/org/opengroup/osdu/notification/api/TestPubsubEndpointHMAC.java @@ -56,6 +56,14 @@ public class TestPubsubEndpointHMAC extends PubsubEndpointHMACTests { // TODO getting SubscriptionException only on ci-cd env super.should_return20XResponseCode_when_makingValidHttpsRequest(); } + + //running against tenant - 'nonexistenttenant' and entitlement throws 403 for invalid teanant + @Override + @Test + public void should_return401_when_noAccessOnCustomerTenant() throws Exception { + ClientResponse response = descriptor.runOnCustomerTenant(getArg(), getOsduTenantAdminCredentials()); + assertEquals(error( response.getEntity(String.class)),403, response.getStatus()); + } } \ No newline at end of file diff --git a/testing/pom.xml b/testing/pom.xml index e19233259c1057a20955126716a59fa71dd85f04..92724d716d54f21bd1aa70cfcacc6aed42992a86 100644 --- a/testing/pom.xml +++ b/testing/pom.xml @@ -18,7 +18,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.opengroup.osdu</groupId> <artifactId>os-notification-testing</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>0.12.0-SNAPSHOT</version> <description>Root Notification Service project</description> <packaging>pom</packaging>