diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b069f39858c1fe97a4129349ee4926b2c4a47103..33733f70338c2f1b0de880dfb7e72a6410fddd08 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -71,13 +71,12 @@ include: - project: "osdu/platform/ci-cd-pipelines" file: "cloud-providers/core-global.yml" - - project: "osdu/platform/ci-cd-pipelines" - file: "publishing/pages.yml" - - local: "devops/gc/pipeline/override-stages.yml" - local: "devops/core-plus/pipeline/override-stages.yml" + - local: "/publish.yml" + .maven: image: maven:3.8.3-openjdk-17-slim tags: ["osdu-medium"] diff --git a/.trufflehog-exclude b/.trufflehog-exclude new file mode 100644 index 0000000000000000000000000000000000000000..66172ba15a450574ebc950fec829deaed995850c --- /dev/null +++ b/.trufflehog-exclude @@ -0,0 +1 @@ +(.*/)?docs/(.*/)? diff --git a/README.md b/README.md index 441332655e7c3f2bd3e27c2a5bfb3488bf383e71..9613376a868ac3fdb5d4d6816832bf9588b77126 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# os-legal +# OSDU Legal Service + +Official documentation is located at [https://osdu.pages.opengroup.org/platform/security-and-compliance/legal/](https://osdu.pages.opengroup.org/platform/security-and-compliance/legal/) ## os-legal-azure diff --git a/docs/api/compliance_openapi.yaml b/docs/api/compliance_openapi.yaml deleted file mode 100644 index 2ab27f2af9cbb7a6d5813e98c0e1bb28d2be517c..0000000000000000000000000000000000000000 --- a/docs/api/compliance_openapi.yaml +++ /dev/null @@ -1,548 +0,0 @@ -swagger: '2.0' -info: - description: APIs to help with legal data governance in the Data Lake. - version: 0.1.0 - title: Legal data governance APIs - contact: - name: OSDU DPS team - email: dps@[OSDU].org -host: legal-osdu-services.appspot.com -x-google-allow: all -x-google-endpoints: - - name: legal-osdu-services.appspot.com - allowCors: 'true' -basePath: /api/legal/v1 -security: - - Bearer: [] - - google_id_token: [] - - sauth_id_token: [] -tags: - - name: LegalTag - description: '' - - name: info - description: "Version info endpoint" -schemes: - - https -consumes: - - application/json -produces: - - application/json -paths: - '/legaltags:properties': - get: - tags: - - LegalTag - summary: Gets LegalTag property values. - description: This allows for the retrieval of allowed values for LegalTag properties. - operationId: getLegalTagProperties - consumes: - - application/json - produces: - - application/json - parameters: - - name: OSDU-Account-Id - in: header - description: Users account e.g. OSDU - required: true - type: string - - name: OSDU-On-Behalf-Of - in: header - description: User's email or auth token - required: false - type: string - responses: - '200': - description: Retrieved proeprties successfully. - schema: - $ref: '#/definitions/ReadablePropertyValues' - '401': - description: You do not have permissions to access this API. - security: - - Bearer: [] - - google_id_token: [] - - sauth_id_token: [] - '/legaltags:validate': - post: - tags: - - LegalTag - summary: Retrieves the invalid LegalTag names with reasons for the given names. - description: This allows for the retrieval of the reason why your LegalTag is not valid. A maximum of 25 can be retrieved at once. - operationId: validateLegalTags - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: body - required: false - schema: - $ref: '#/definitions/RequestLegalTags' - - name: OSDU-Account-Id - in: header - description: Users account e.g. OSDU - required: true - type: string - - name: OSDU-On-Behalf-Of - in: header - description: User's email or auth token - required: false - type: string - responses: - '200': - description: Retrieved LegalTag names with reason successfully. - schema: - $ref: '#/definitions/InvalidTagsWithReason' - '400': - description: Invalid parameters were given on request. - '401': - description: You do not have permissions to access this API. - '404': - description: LegalTag names were not found. - security: - - Bearer: [] - - google_id_token: [] - - sauth_id_token: [] - '/legaltags:batchRetrieve': - post: - tags: - - LegalTag - summary: Retrieves the LegalTags for the given names. - description: This allows for the retrieval of your LegalTags using the 'name' associated with it. A maximum of 25 can be retrieved at once. - operationId: getLegalTags - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: body - required: false - schema: - $ref: '#/definitions/RequestLegalTags' - - name: OSDU-Account-Id - in: header - description: Users account e.g. OSDU - required: true - type: string - - name: OSDU-On-Behalf-Of - in: header - description: User's email or auth token - required: false - type: string - responses: - '200': - description: Retrieved LegalTags successfully. - schema: - $ref: '#/definitions/LegalTagDtos' - '400': - description: Invalid parameters were given on request. - '401': - description: You do not have permissions to access this API. - '404': - description: One or more requested LegalTags were not found. - security: - - Bearer: [] - - google_id_token: [] - - sauth_id_token: [] - /legaltags: - get: - tags: - - LegalTag - summary: Gets all LegalTags. - description: This allows for the retrieval of all LegalTags. - operationId: listLegalTags - consumes: - - application/json - produces: - - application/json - parameters: - - name: valid - in: query - description: 'If true returns only valid LegalTags, if false returns only invalid LegalTags. Default value is true.' - required: false - type: boolean - default: true - - name: OSDU-Account-Id - in: header - description: Users account e.g. OSDU - required: true - type: string - - name: OSDU-On-Behalf-Of - in: header - description: User's email or auth token - required: false - type: string - responses: - '200': - description: Retrieved LegalTags successfully. - schema: - $ref: '#/definitions/LegalTagDtos' - '400': - description: Invalid parameters were given on request. - '401': - description: You do not have permissions to access this API. - security: - - Bearer: [] - - google_id_token: [] - - sauth_id_token: [] - post: - tags: - - LegalTag - summary: Creates the LegalTag for the given 'name'. - description: This allows for the creation of your LegalTag. There can only be 1 LegalTag per 'name'. A LegalTag must be created before you can start ingesting data for that name. - operationId: createLegalTag - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: body - required: false - schema: - $ref: '#/definitions/LegalTagDto' - - name: OSDU-Account-Id - in: header - description: Users account e.g. OSDU - required: true - type: string - - name: OSDU-On-Behalf-Of - in: header - description: User's email or auth token - required: false - type: string - responses: - '201': - description: Created LegalTag successfully. - schema: - $ref: '#/definitions/LegalTagDto' - '400': - description: Invalid parameters were given on request. - '401': - description: You do not have permissions to access this API. - '409': - description: A LegalTag with the given name already exists. - security: - - Bearer: [] - - google_id_token: [] - - sauth_id_token: [] - put: - tags: - - LegalTag - summary: Updates the LegalTag for the given 'name'. - description: This allows to update certain properties of your LegalTag using the 'name' associated with it. - operationId: updateLegalTag - consumes: - - application/json - produces: - - application/json - parameters: - - in: body - name: body - required: false - schema: - $ref: '#/definitions/UpdateLegalTag' - - name: OSDU-Account-Id - in: header - description: Users account e.g. OSDU - required: true - type: string - - name: OSDU-On-Behalf-Of - in: header - description: User's email or auth token - required: false - type: string - responses: - '200': - description: Updated LegalTag successfully. - schema: - $ref: '#/definitions/LegalTagDto' - '400': - description: Invalid parameters were given on request. - '401': - description: You do not have permissions to access this API. - '404': - description: Requested LegalTag to update was not found. - security: - - Bearer: [] - - google_id_token: [] - - sauth_id_token: [] - '/legaltags/{name}': - get: - tags: - - LegalTag - summary: Gets a LegalTag for the given 'name'. - description: This allows for the retrieval of your LegalTag using the 'name' associated with it. - operationId: getLegalTag - consumes: - - application/json - produces: - - application/json - parameters: - - name: name - in: path - required: true - type: string - x-example: OSDU-Private-USA-EHC - - name: OSDU-Account-Id - in: header - description: Users account e.g. OSDU - required: true - type: string - - name: OSDU-On-Behalf-Of - in: header - description: User's email or auth token - required: false - type: string - responses: - '200': - description: Retrieved LegalTag successfully. - schema: - $ref: '#/definitions/LegalTagDto' - '400': - description: Invalid parameters were given on request. - '401': - description: You do not have permissions to access this API. - '404': - description: Requested LegalTag was not found. - security: - - Bearer: [] - - google_id_token: [] - - sauth_id_token: [] - /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: "#/definitions/VersionInfo" -securityDefinitions: - Bearer: - type: apiKey - name: Authorization - in: header - sauth_id_token: - authorizationUrl: 'https://opsauth-dot-osduauth-preview.appspot.com/v1/auth' - flow: implicit - type: oauth2 - x-google-issuer: sauth-preview.OSDU.com - x-google-jwks_uri: 'https://opsauth-dot-osduauth-preview.appspot.com/v1/certs' - scopes: {} - google_id_token: - authorizationUrl: '' - flow: implicit - type: oauth2 - x-google-issuer: 'https://accounts.google.com' - x-google-jwks_uri: 'https://www.googleapis.com/oauth2/v3/certs' - scopes: {} -definitions: - ReadablePropertyValues: - type: object - properties: - countriesOfOrigin: - type: object - description: The values of all the allowed Countries of Origin with the ISO Alpha 2 code and country name. - additionalProperties: - type: string - otherRelevantDataCountries: - type: object - description: The values of all the allowed Other Relevant Data Countries with the ISO Alpha 2 code and country name. - additionalProperties: - type: string - securityClassifications: - type: array - description: The values of all the allowed Security Classifications. - uniqueItems: true - items: - type: string - exportClassificationControlNumbers: - type: array - description: The name of all the allowed Export Classifications. - uniqueItems: true - items: - type: string - personalDataTypes: - type: array - description: The name of all the allowed Personal Data Type values. - uniqueItems: true - items: - type: string - description: Shows the allowed values of the fields of a LegalTag. - InvalidTagWithReason: - type: object - properties: - name: - type: string - description: The name of the LegalTag. - reason: - type: string - description: The reason the LegalTag is currently invalid. - description: Represents a single invalid LegalTag. - InvalidTagsWithReason: - type: object - properties: - invalidLegalTags: - type: array - description: A collection of invalid LegalTags. - items: - $ref: '#/definitions/InvalidTagWithReason' - description: Represents a collection invalid LegalTags. - RequestLegalTags: - type: object - required: - - names - properties: - names: - type: array - description: The name of all the LegalTags to retrieve. - items: - type: string - maxItems: 25 - minItems: 1 - description: The model to retrieve multiple LegalTags in batch. - LegalTagDto: - type: object - properties: - name: - type: string - example: OSDU-Private-EHCData - description: The name of the LegalTag. - description: - type: string - description: The description of the LegalTag. - properties: - $ref: '#/definitions/Properties' - description: Represents a single LegalTag. - LegalTagDtos: - type: object - properties: - legalTags: - type: array - description: A collection of complete LegalTags - items: - $ref: '#/definitions/LegalTagDto' - description: Represents a collection of LegalTags. - Properties: - type: object - required: - - contractId - - countryOfOrigin - - dataType - - exportClassification - - originator - - personalData - - securityClassification - properties: - countryOfOrigin: - type: array - example: US - description: The ISO Alpha 2 country code(s) of where the data relates to. - items: - type: string - contractId: - type: string - example: No Contract Related - description: The Id of the physical contract associated with the data being ingested. - expirationDate: - type: string - example: '2025-12-25' - description: The optional expiration date of the contract in the format YYYY-MM-DD - originator: - type: string - example: Schlumberger - description: The company who owns the data. - dataType: - type: string - example: Third Party Data - description: The type of data being ingested. - securityClassification: - type: string - example: Private - description: The security classification of the data. - personalData: - type: string - example: No Personal Data - description: Whether the data contains any personally identifiable data. - exportClassification: - type: string - example: EAR99 - description: The ECCN value of the data if one applies. - extensionProperties: - type: object - additionalProperties: true - description: The optional object field to attach any company specific attributes. - description: LegalTag properties - UpdateLegalTag: - type: object - required: - - contractId - - name - properties: - name: - type: string - description: The name of the LegalTag. - contractId: - type: string - example: No Contract Related - description: The Id of the physical contract associated with the data being ingested. - description: - type: string - description: The optional description if the LegalTag to allow for easier discoverability of Legaltags overtime. - expirationDate: - type: string - example: '2025-12-25' - description: The optional expiration date of the contract in the format YYYY-MM-DD. - extensionProperties: - type: object - additionalProperties: true - description: The optional object field to attach any company specific attributes. - description: The model to update an existing LegalTag - 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: "#/definitions/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/api/legal_openapi.yaml b/docs/api/legal_openapi.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0b593b7a54a11b9d6387cdd94d01259c9cb38aa0 --- /dev/null +++ b/docs/api/legal_openapi.yaml @@ -0,0 +1,1156 @@ +openapi: 3.0.1 +info: + title: Legal Service + description: Legal Service provides APIs to help with legal data governance in the Data Lake. + contact: + name: OSDU Data Platform Team + email: dps@OSDU.org + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.0 +servers: + - url: /api/legal/v1/ +security: + - Authorization: [] +tags: + - name: health + description: Health related endpoints + - name: legaltag + description: LegalTags related endpoints + - name: legaltag-status-job + description: LegalTags status Job related endpoints + - name: info + description: Version info endpoint +paths: + /legaltags: + get: + tags: + - legaltag + summary: Gets all LegalTags. + description: This allows for the retrieval of all LegalTags. + operationId: listLegalTags + parameters: + - name: valid + in: query + description: If true returns only valid LegalTags, if false returns only invalid LegalTags. Default value is true. + required: false + schema: + type: boolean + default: true + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + responses: + '200': + description: Retrieved LegalTags successfully. + content: + '*/*': + schema: + $ref: '#/components/schemas/LegalTagDtos' + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: Requested LegalTag to update was not found. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + put: + tags: + - legaltag + summary: Updates the LegalTag for the given `name`. + description: This allows to update certain properties of your LegalTag using the `name` associated with it. + operationId: updateLegalTag + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateLegalTag' + required: true + responses: + '200': + description: Updated LegalTag successfully. + content: + '*/*': + schema: + $ref: '#/components/schemas/LegalTagDto' + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: Requested LegalTag to update was not found. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '409': + description: A LegalTag with the given name already exists. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + post: + tags: + - legaltag + summary: Creates the LegalTag for the given `name`. + description: This allows for the creation of your LegalTag. There can only be 1 LegalTag per `name`. A LegalTag must be created before you can start ingesting data for that name. + operationId: createLegalTag + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LegalTagDto' + required: true + responses: + '201': + description: Created LegalTag successfully. + content: + '*/*': + schema: + $ref: '#/components/schemas/LegalTagDto' + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: Not Found + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '409': + description: A LegalTag with the given name already exists. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + /legaltags:validate: + post: + tags: + - legaltag + summary: Retrieves the invalid LegalTag names with reasons for the given `names`. + description: This allows for the retrieval of the reason why your LegalTag is not valid. A maximum of 25 can be retrieved at once. + operationId: validateLegalTags + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RequestLegalTags' + required: true + responses: + '200': + description: Retrieved LegalTag names with reason successfully. + content: + '*/*': + schema: + $ref: '#/components/schemas/InvalidTagsWithReason' + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: LegalTag names were not found. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + /legaltags:query: + post: + tags: + - legaltag + summary: Retrieves the legaltags which matches search criteria or none if there is no match + description: This allows search for specific attributes of legaltags including the attributes of extensionproperties + operationId: searchLegalTag + parameters: + - name: valid + in: query + description: If true returns only valid LegalTags, if false returns only invalid LegalTags. Default value is true. + required: false + schema: + type: boolean + default: true + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SearchLegalTag' + required: true + responses: + '200': + description: Retrieved LegalTags successfully. + content: + '*/*': + schema: + $ref: '#/components/schemas/LegalTagDtos' + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: Requested LegalTag to update was not found. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + /legaltags:batchRetrieve: + post: + tags: + - legaltag + summary: Retrieves the LegalTags for the given `names`. + description: This allows for the retrieval of your LegalTags using the `name` associated with it. A maximum of 25 can be retrieved at once. + operationId: getLegalTags + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RequestLegalTags' + required: true + responses: + '200': + description: Retrieved LegalTags successfully. + content: + '*/*': + schema: + $ref: '#/components/schemas/LegalTagDtos' + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: One or more requested LegalTags were not found. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + /legaltags:properties: + get: + tags: + - legaltag + summary: Gets LegalTag property values. + description: This allows for the retrieval of allowed values for LegalTag properties. + operationId: getLegalTagProperties + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + responses: + '200': + description: Retrieved LegalTag properties successfully. + content: + '*/*': + schema: + $ref: '#/components/schemas/ReadablePropertyValues' + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + /legaltags/{name}: + get: + tags: + - legaltag + summary: Gets a LegalTag for the given `name`. + description: This allows for the retrieval of your LegalTag using the `name` associated with it. + operationId: getLegalTag + parameters: + - name: name + in: path + description: Name of the LegalTag + required: true + schema: + type: string + example: OSDU-Private-USA-EHC + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + responses: + '200': + description: Retrieved LegalTag successfully. + content: + '*/*': + schema: + $ref: '#/components/schemas/LegalTagDto' + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: Requested LegalTag was not found. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + delete: + tags: + - legaltag + summary: Deletes a LegalTag for the given `name`. + description: This allows for the deletion of your LegalTag with the given `name`. This makes the given legaltags data invalid. + operationId: deleteLegalTag + parameters: + - name: name + in: path + description: Name of the LegalTag to delete + required: true + schema: + type: string + example: OSDU-Private-USA-EHC + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + responses: + '204': + description: LegalTag deleted successfully. + content: + '*/*': + schema: + type: string + enum: + - 100 CONTINUE + - 101 SWITCHING_PROTOCOLS + - 102 PROCESSING + - 103 CHECKPOINT + - 200 OK + - 201 CREATED + - 202 ACCEPTED + - 203 NON_AUTHORITATIVE_INFORMATION + - 204 NO_CONTENT + - 205 RESET_CONTENT + - 206 PARTIAL_CONTENT + - 207 MULTI_STATUS + - 208 ALREADY_REPORTED + - 226 IM_USED + - 300 MULTIPLE_CHOICES + - 301 MOVED_PERMANENTLY + - 302 FOUND + - 302 MOVED_TEMPORARILY + - 303 SEE_OTHER + - 304 NOT_MODIFIED + - 305 USE_PROXY + - 307 TEMPORARY_REDIRECT + - 308 PERMANENT_REDIRECT + - 400 BAD_REQUEST + - 401 UNAUTHORIZED + - 402 PAYMENT_REQUIRED + - 403 FORBIDDEN + - 404 NOT_FOUND + - 405 METHOD_NOT_ALLOWED + - 406 NOT_ACCEPTABLE + - 407 PROXY_AUTHENTICATION_REQUIRED + - 408 REQUEST_TIMEOUT + - 409 CONFLICT + - 410 GONE + - 411 LENGTH_REQUIRED + - 412 PRECONDITION_FAILED + - 413 PAYLOAD_TOO_LARGE + - 413 REQUEST_ENTITY_TOO_LARGE + - 414 URI_TOO_LONG + - 414 REQUEST_URI_TOO_LONG + - 415 UNSUPPORTED_MEDIA_TYPE + - 416 REQUESTED_RANGE_NOT_SATISFIABLE + - 417 EXPECTATION_FAILED + - 418 I_AM_A_TEAPOT + - 419 INSUFFICIENT_SPACE_ON_RESOURCE + - 420 METHOD_FAILURE + - 421 DESTINATION_LOCKED + - 422 UNPROCESSABLE_ENTITY + - 423 LOCKED + - 424 FAILED_DEPENDENCY + - 425 TOO_EARLY + - 426 UPGRADE_REQUIRED + - 428 PRECONDITION_REQUIRED + - 429 TOO_MANY_REQUESTS + - 431 REQUEST_HEADER_FIELDS_TOO_LARGE + - 451 UNAVAILABLE_FOR_LEGAL_REASONS + - 500 INTERNAL_SERVER_ERROR + - 501 NOT_IMPLEMENTED + - 502 BAD_GATEWAY + - 503 SERVICE_UNAVAILABLE + - 504 GATEWAY_TIMEOUT + - 505 HTTP_VERSION_NOT_SUPPORTED + - 506 VARIANT_ALSO_NEGOTIATES + - 507 INSUFFICIENT_STORAGE + - 508 LOOP_DETECTED + - 509 BANDWIDTH_LIMIT_EXCEEDED + - 510 NOT_EXTENDED + - 511 NETWORK_AUTHENTICATION_REQUIRED + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: Requested LegalTag to delete was not found. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + /jobs/updateLegalTagStatus: + get: + tags: + - legaltag-status-job + summary: Check LegalTag Compliance Job Status + description: To check LegalTag Compliance Job Status. + operationId: checkLegalTagStatusChanges + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + responses: + '200': + description: OK + content: + '*/*': + schema: + type: string + enum: + - 100 CONTINUE + - 101 SWITCHING_PROTOCOLS + - 102 PROCESSING + - 103 CHECKPOINT + - 200 OK + - 201 CREATED + - 202 ACCEPTED + - 203 NON_AUTHORITATIVE_INFORMATION + - 204 NO_CONTENT + - 205 RESET_CONTENT + - 206 PARTIAL_CONTENT + - 207 MULTI_STATUS + - 208 ALREADY_REPORTED + - 226 IM_USED + - 300 MULTIPLE_CHOICES + - 301 MOVED_PERMANENTLY + - 302 FOUND + - 302 MOVED_TEMPORARILY + - 303 SEE_OTHER + - 304 NOT_MODIFIED + - 305 USE_PROXY + - 307 TEMPORARY_REDIRECT + - 308 PERMANENT_REDIRECT + - 400 BAD_REQUEST + - 401 UNAUTHORIZED + - 402 PAYMENT_REQUIRED + - 403 FORBIDDEN + - 404 NOT_FOUND + - 405 METHOD_NOT_ALLOWED + - 406 NOT_ACCEPTABLE + - 407 PROXY_AUTHENTICATION_REQUIRED + - 408 REQUEST_TIMEOUT + - 409 CONFLICT + - 410 GONE + - 411 LENGTH_REQUIRED + - 412 PRECONDITION_FAILED + - 413 PAYLOAD_TOO_LARGE + - 413 REQUEST_ENTITY_TOO_LARGE + - 414 URI_TOO_LONG + - 414 REQUEST_URI_TOO_LONG + - 415 UNSUPPORTED_MEDIA_TYPE + - 416 REQUESTED_RANGE_NOT_SATISFIABLE + - 417 EXPECTATION_FAILED + - 418 I_AM_A_TEAPOT + - 419 INSUFFICIENT_SPACE_ON_RESOURCE + - 420 METHOD_FAILURE + - 421 DESTINATION_LOCKED + - 422 UNPROCESSABLE_ENTITY + - 423 LOCKED + - 424 FAILED_DEPENDENCY + - 425 TOO_EARLY + - 426 UPGRADE_REQUIRED + - 428 PRECONDITION_REQUIRED + - 429 TOO_MANY_REQUESTS + - 431 REQUEST_HEADER_FIELDS_TOO_LARGE + - 451 UNAVAILABLE_FOR_LEGAL_REASONS + - 500 INTERNAL_SERVER_ERROR + - 501 NOT_IMPLEMENTED + - 502 BAD_GATEWAY + - 503 SERVICE_UNAVAILABLE + - 504 GATEWAY_TIMEOUT + - 505 HTTP_VERSION_NOT_SUPPORTED + - 506 VARIANT_ALSO_NEGOTIATES + - 507 INSUFFICIENT_STORAGE + - 508 LOOP_DETECTED + - 509 BANDWIDTH_LIMIT_EXCEEDED + - 510 NOT_EXTENDED + - 511 NETWORK_AUTHENTICATION_REQUIRED + '400': + description: Bad Request + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '401': + description: Unauthorized + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '403': + description: User not authorized to perform the action. + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '404': + description: Not Found + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '500': + description: Internal Server Error + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '502': + description: Bad Gateway + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + '503': + description: Service Unavailable + content: + '*/*': + schema: + $ref: '#/components/schemas/AppError' + security: + - Authorization: [] + /info: + get: + tags: + - info + summary: Version info + description: For deployment available public `/info` endpoint, which provides build and git related information. + operationId: info + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + responses: + '200': + description: Version info. + content: + application/json: + schema: + $ref: '#/components/schemas/VersionInfo' + /_ah/readiness_check: + get: + tags: + - health + summary: Readiness Check endpoint + description: For deployment available public `/readiness_check` endpoint, which provides `Legal service is ready` message. + operationId: readinessCheck + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + responses: + '200': + description: Legal service is ready + content: + '*/*': + schema: + type: string + /_ah/liveness_check: + get: + tags: + - health + summary: Liveness Check endpoint + description: For deployment available public `/liveness_check` endpoint, which provides `Legal service is alive` message. + operationId: livenessCheck + parameters: + - name: data-partition-id + in: header + description: Tenant Id + required: true + schema: + type: string + responses: + '200': + description: Legal service is alive + content: + '*/*': + schema: + type: string +components: + schemas: + UpdateLegalTag: + type: object + properties: + name: + type: string + description: The name of the LegalTag + example: OSDU-Private-EHCData + contractId: + type: string + description: The Id of the physical contract associated with the data being ingested. + example: No Contract Related + description: + type: string + description: The optional description if the LegalTag to allow for easier discoverability of Legaltags overtime. + expirationDate: + type: string + description: The optional expiration date of the contract in the format YYYY-MM-DD + format: date-time + extensionProperties: + type: object + additionalProperties: + type: object + description: The optional object field to attach any company specific attributes. + description: The optional object field to attach any company specific attributes. + description: The model to update an existing LegalTag + AppError: + type: object + properties: + code: + type: integer + format: int32 + reason: + type: string + message: + type: string + LegalTagDto: + type: object + properties: + name: + type: string + description: The name of the LegalTag + example: OSDU-Private-EHCData + description: + type: string + description: The description of the LegalTag + properties: + $ref: '#/components/schemas/Properties' + description: Represents a single LegalTag + Properties: + type: object + properties: + countryOfOrigin: + type: array + items: + type: string + contractId: + type: string + expirationDate: + type: string + format: date-time + originator: + type: string + dataType: + type: string + securityClassification: + type: string + personalData: + type: string + exportClassification: + type: string + extensionProperties: + type: object + additionalProperties: + type: object + description: LegalTag properties + RequestLegalTags: + required: + - names + type: object + properties: + names: + maxItems: 25 + minItems: 1 + type: array + description: The name of all the LegalTags to retrieve. + items: + type: string + description: The name of all the LegalTags to retrieve. + description: The model to retrieve multiple LegalTags in batch. + InvalidTagWithReason: + type: object + properties: + name: + type: string + description: The name of the LegalTag. + reason: + type: string + description: The reason the LegalTag is currently invalid. + description: Represents a single invalid LegalTag. + InvalidTagsWithReason: + type: object + properties: + invalidLegalTags: + type: array + description: A collection of invalid LegalTags + items: + $ref: '#/components/schemas/InvalidTagWithReason' + description: Represents a collection of invalid LegalTags. + SearchLegalTag: + type: object + properties: + queryList: + type: array + description: Filter condition query + items: + type: string + description: Filter condition query + operatorList: + type: array + description: If there are multiple conditions need to be joined in by logical operators + items: + type: string + description: If there are multiple conditions need to be joined in by logical operators + sortBy: + type: string + sortOrder: + type: string + limit: + type: integer + format: int32 + description: Represents the Search Query objects for Legaltags. + LegalTagDtos: + type: object + properties: + legalTags: + type: array + description: A collection of complete LegalTags + items: + $ref: '#/components/schemas/LegalTagDto' + description: Represents a collection of LegalTags. + ReadablePropertyValues: + type: object + properties: + countriesOfOrigin: + type: object + additionalProperties: + type: string + description: The values of all the allowed Countries of Origin with the ISO Alpha 2 code and country name. + description: The values of all the allowed Countries of Origin with the ISO Alpha 2 code and country name. + otherRelevantDataCountries: + type: object + additionalProperties: + type: string + description: The values of all the allowed Other Relevant Data Countries with the ISO Alpha 2 code and country name. + description: The values of all the allowed Other Relevant Data Countries with the ISO Alpha 2 code and country name. + securityClassifications: + uniqueItems: true + type: array + description: The values of all the allowed Security Classifications. + items: + type: string + description: The values of all the allowed Security Classifications. + exportClassificationControlNumbers: + uniqueItems: true + type: array + description: The name of all the allowed Export Classifications. + items: + type: string + description: The name of all the allowed Export Classifications. + personalDataTypes: + uniqueItems: true + type: array + description: The name of all the allowed Personal Data Type values. + items: + type: string + description: The name of all the allowed Personal Data Type values. + dataTypes: + uniqueItems: true + type: array + description: The name of all the allowed Data Type values. + items: + type: string + description: The name of all the allowed Data Type values. + description: Shows the allowed values of the fields of a LegalTag. + ConnectedOuterService: + type: object + properties: + name: + type: string + version: + type: string + VersionInfo: + type: object + properties: + groupId: + type: string + artifactId: + type: string + version: + type: string + buildTime: + type: string + branch: + type: string + commitId: + type: string + commitMessage: + type: string + connectedOuterServices: + type: array + items: + $ref: '#/components/schemas/ConnectedOuterService' + securitySchemes: + Authorization: + type: http + scheme: bearer + bearerFormat: Authorization diff --git a/docs/docs/AllowDataIngestionFromCertainCOO.md b/docs/docs/AllowDataIngestionFromCertainCOO.md new file mode 100644 index 0000000000000000000000000000000000000000..a2ae8d493ca824481f15c0c99d9d233c115248ff --- /dev/null +++ b/docs/docs/AllowDataIngestionFromCertainCOO.md @@ -0,0 +1,37 @@ +# Allow Data Ingestion from Specific Country of Origin + +We can allow certain country as a valid COO(Country of Origin) by uploading customized legal configuration file into cloud storage. It will override the default configuration for legal service. + +## Configuration Steps + +Following steps can be taken to allow a specific COO: + +1. Refer to following example and create a `Legal_COO.json` file: +``` + [{ + "name": "Brazil", + "alpha2": "BR", + "numeric": 76, + "residencyRisk": "Client consent required", + "typesNotApplyDataResidency": ["Transferred Data"] + }] +``` + +To obtain the proper alpha, numeric code, and data types without applying data residency constraints (`alpha2`, `numeric`, `typesNotApplyDataResidency` fields), please refer to the [Legal Service Default Configuration](https://community.opengroup.org/osdu/platform/security-and-compliance/legal/-/blob/master/legal-core/src/main/resources/DefaultCountryCode.json) +then enter the appropriate restriction level in the `residencyRisk` field. + +!!! Note "Residency Risk" + + Usually we set `residencyRisk` to `Client consent required` when we are allowing data from this country as an exception, but please consult with business if this is the correct value here. + +2. Go to cloud storage location where we store legal config file. For example `Azure resource group - dev dp1`: + + + +3. Go to the `legal-service-azure-configuration` container and upload the `Legal_COO.json` file created in step 1, replace the existing item with the same name if existed: + +  + +!!! Note "Make sure filename matches exactly" + + Please make sure the file name is exactly the same with the existing file `Legal_COO.json`, it's case-sensitive. \ No newline at end of file diff --git a/docs/tutorial/ComplianceService.md b/docs/docs/api.md similarity index 58% rename from docs/tutorial/ComplianceService.md rename to docs/docs/api.md index 30e3b2a476d6b9e80f6338e14a1ac640310e21f8..6769c3f40c161f1521cc2962e6c3dc2a00b1366a 100644 --- a/docs/tutorial/ComplianceService.md +++ b/docs/docs/api.md @@ -1,42 +1,9 @@ -## Compliance Service - - -## Table of Contents <a name="TOC"></a> -* [Introduction](#Introduction) -* [API usage](#API-usage) -* [What is a LegalTag?](#What-is-a-LegalTag) -* [Ingestion workflow](#Ingestion-workflow) -* [Creating a LegalTag](#Creating-a-LegalTag) -* [LegalTag properties](#LegalTag-properties) -* [Creating a Record](#Creating-a-Record) -* [What are Derivatives?](#What-are-Derivatives) -* [Validating a LegalTag](#Validating-a-LegalTag) -* [Updating a LegalTag](#Updating-a-LegalTag) -* [Compliance on consumption](#Compliance-on-consumption) -* [The LegalTag Changed notification](#The-LegalTag-Changed-notification) -* [Permissions](#Permissions) -* [Version info endpoint](#version-info-endpoint) - -## Introduction<a name="Introduction"></a> - -This document covers how to remain compliant at the different stages of the data lifecycle inside the Data Ecosystem. - -1. When ingesting data -2. Whilst the data is inside the Data Ecosystem -3. When consuming data - -The clients' interaction revolves around ingestion and consumption, so this is when you need to use what is contained in this guide. Point 2 should be mostly handled on the clients’ behalf; however, it is still important to understand that this is happening as it has ramifications on when and how data can be consumed. - -Data compliance is largely governed through the Records in the storage service. Though there is an independent legal service and LegalTags entity, these offer no compliance by themselves. +# API -Records have a Legal section in their schema and this is where the compliance is enforced. However, clients must still make sure they are using the Record service correctly to remain compliant. - -Further details can be found in the [Creating a Record](#Creating-a-Record) section. - -## API usage<a name="API-usage"></a> +## API Usage Details of our APIs including how to create and retrieve LegalTags can be found [here.](https://community.opengroup.org/osdu/platform/security-and-compliance/legal/-/blob/5ecde60781c5ce0c92579e87c4b30fdf9219a9ab/docs/api/compliance_openapi.yaml) -##### Permissions +### Permissions | **_API_** | **_Minimum Permissions Required_** | | --- | --- | @@ -44,7 +11,7 @@ Details of our APIs including how to create and retrieve LegalTags can be found | Create a LegalTag | users.datalake.editors | | Update a LegalTag | users.datalake.editors | -##### Headers +### Headers | **_Header_** | **_Description_** | | --- | --- | @@ -53,35 +20,13 @@ Details of our APIs including how to create and retrieve LegalTags can be found The Data Ecosystem stores data in different data partitions, depending on the access to those data partitions in the OSDU system. A user may have access to one or more data partitions. -[Back to table of contents](#TOC) - -## What is a LegalTag?<a name="What-is-a-LegalTag"></a> -A LegalTag is the entity that represents the legal status of data in the Data Ecosystem. It is a collection of *properties* that governs how the data can be consumed and ingested. - -A legal tag is required for data ingestion. Therefore, creation of a legal tag is a necessary first step if there isn't a legal tag already exists for use with the ingested data. The LegalTag name needs to be assigned to the LegalTag during creation, and is used for reference. The name is the unique identifier for the LegalTag that is used to access it. - -When data is ingested, it is assigned the LegalTag *name*. This name is checked for a corresponding valid LegalTag in the system. A valid LegalTag means it exists and has not expired. If a LegalTag is invalid, the data is rejected. For instance, we may not allow ingestion of data from a certain country, or we may not allow consumption of data that has an expired contract. - -In the same manner, the ingested data will be invalidated (soft-deleted) when the legal tag expires, as it would no longer be compliant. - - -## Ingestion workflow<a name="Ingestion-workflow"></a> +## Creating a LegalTag - - -[](url) -The above diagram shows the typical sequence of events of a data ingestion. The important points to highlight are as follow: - -* It is the ingestor's responsibility to create a LegalTag. LegalTag validation happens at this point. -* The Storage service validates the LegalTag for the data being ingested. -* Only after validating a LegalTag exists can we ingest data. No data should be stored at any point in the Data Ecosystem that does not have a valid LegalTag. - -## Creating a LegalTag<a name="Creating-a-LegalTag"></a> Any data being ingested needs a LegalTag associated with it. You can create a LegalTag by using the POST LegalTag API e.g. POST /api/legal/v1/legaltags -<details><summary>Curl</summary> +<details><summary>Curl Post legaltags Example</summary> ``` curl --request POST \ @@ -111,25 +56,40 @@ curl --request POST \ </details> -It is good practice for LegalTag names to be clear and descriptive of the properties it represents, so it would be easy to discover and to associate to the correct data with it. Also, the description field is a free form optional field to allow for you to add context to the LegalTag, making easier to understand and retrieve over time. +!!! Tip + + It is good practice for LegalTag names to be clear and descriptive of the properties it represents, so it would be easy to discover and to associate to the correct data with it. Also, the description field is a free form optional field to allow for you to add context to the LegalTag, making easier to understand and retrieve over time. The "extensionProperties" field is an optional json object field and you may add any company specific attributes inside this field. When creating LegalTags, the name is automatically prefixed with the data-partition-name that is assigned to the partition. So in the example above, if the given data-partition-name is **mypartition**, then the actual name of the LegalTag would be **mypartition-demo-legaltag**. -Valid values: The legalTag name needs to be between 3 and 100 characters and only alphanumeric characters and hyphens are allowed +!!! Info "Legal Tag Names" + + The legalTag name needs to be between 3 and 100 characters and only alphanumeric characters and hyphens are allowed. To help with LegalTag creation, it is advised to use the Get LegalTag Properties API to obtain the allowed properties before creating a legal tag. This returns the allowed values for many of the LegalTag properties. -## LegalTag properties<a name="LegalTag-properties"></a> +## LegalTag Properties + Below are details of the properties you can supply when creating a LegalTag along with the values you can use. The allowed properties values can be data partition specific. Valid values associated with the property are shown. All values are mandatory unless otherwise stated. You can get the data partition's specific allowed properties values by using LegalTag Properties API e.g. GET /api/legal/v1/legaltags:properties + +<details><summary>Curl Get legaltags:properties Example</summary> +``` +curl --request GET \ + --url '/api/legal/v1/legaltags:properties' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer <JWT>' \ + --header 'content-type: application/json' \ + --header 'data-partition-id: opendes' \ +``` +</details> <details><summary>Example 200 Response</summary> - ``` { "countriesOfOrigin": { @@ -161,46 +121,46 @@ You can get the data partition's specific allowed properties values by using Leg </details> -<details><summary>Curl</summary> - -``` -curl --request GET \ - --url '/api/legal/v1/legaltags:properties' \ - --header 'accept: application/json' \ - --header 'authorization: Bearer <JWT>' \ - --header 'content-type: application/json' \ - --header 'data-partition-id: opendes' \ -``` -</details> +### Country of Origin -#### Country of origin - Valid values: An array of ISO Alpha-2 country code. This is normally one value but can be more. This is required. +Valid values: An array of [ISO Alpha-2 country codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). This is normally one value but can be more. This field is required. -Notes: This is the country from where the data originally came, NOT from where the data was sent. The list of allowed countries is below. If ingesting Third Party Data, you can ingest data from any country that is not embargoed, if you have a valid contract associated with it that allows for this. This property is case sensitive. +!!! Note "Country of Origin" + + This is the country from where the data originally came, NOT from where the data was sent. The list of default allowed countries is defined [here](https://community.opengroup.org/osdu/platform/security-and-compliance/legal/-/blob/master/legal-core/src/main/resources/DefaultCountryCode.json?ref_type=heads). If ingesting Third Party Data, you can ingest data from any country that is not embargoed, if you have a valid contract associated with it that allows for this. This property is case sensitive. + +### Contract Id -#### Contract Id Valid values: This should be the Contract Id associated with the data or 'Unknown' or 'No Contract Related'. The contract ID must be between 3 and 40 characters and only include alphanumeric values and hyphens. - - Notes: This is always required for any data types. This property is case sensitive. + +!!! Note "ContractId" + + This is always required for any data types. This property is case sensitive. -#### Expiration date +### Expiration Date + Valid values: Any date in the future in the format yyyy-MM-dd (e.g. 2099-12-25) or empty. - When the provided contract ID is "Unknown" or "No Contract Related", then the expiration date could be empty. However, if the date is provided, then it will be honored in validating the legal tag and the associated data, even when there's no contract is provided. As such, when the legal tag expires, the associated data will be soft-deleted from the Data Ecosystem. + When the provided contract ID is "Unknown" or "No Contract Related", then the _expiration date_ could be empty. However, if the date is provided, then it will be honored in validating the legal tag and the associated data, even when there's no contract is provided. As such, when the legal tag expires, the associated data will be soft-deleted from the Data Ecosystem. - Notes: This sets the inclusive date when the LegalTag expires and the data it relates to is no longer usable in the Data Ecosystem. This normally is taken from the physical contracts expiration date i.e. when you supply a contract ID. This is non mandatory field, but is required for certain types of data e.g. 3rd party. If the field is not set it will be autopopulated with the value 9999-12-31. This property is case sensitive. +!!! Note "Expiration Date" + + This sets the inclusive date when the LegalTag expires and the data it relates to is no longer usable in the Data Ecosystem. This normally is taken from the physical contracts _expiration date_ i.e. when you supply a contract ID. This is non mandatory field, but is required for certain types of data e.g. 3rd party. If the field is not set it will be autopopulated with the value 9999-12-31. This property is case sensitive. -#### Originator - Valid values: Should be the name of the client or supplier. +### Originator + +Valid values: Should be the name of the client or supplier. - Notes: This is always required. This property is case sensitive. +!!! Note "Originator" + + This is always required. This property is case sensitive. -#### Data type +### Data type - | dataType | Data Residency Restriction | +| dataType | Data Residency Restriction | | ----------- | -------- | | "Public Domain Data" | "public data, no contract required" | | "First Party Data" | "partition owner's data, no contract required" | @@ -209,27 +169,52 @@ Notes: This is the country from where the data originally came, NOT from where t | "Transferred Data" | "EHC/Index data, no contract required" | - Notes: Different data types are allowed dependent on the data partitions e.g. vendor partitions have different governing rules as opposed to standard partitions. To list the allowed data types for your data partition use the [LegalTag Properties](#LegalTag-properties). +!!! Note "Data Types" + + The data types that are allowed are dependent on the data partitions. Vendor partitions may have different governing rules as opposed to standard partitions. To list the allowed data types for your data partition use the `GET /api/legal/v1/legaltags:properties` as described in [properties](#legaltag-properties). -#### Security classification - Valid values: 'Public', 'Private', 'Confidential' +### Security classification + +Valid values: + + - `Public`, + - `Private`, + - `Confidential` - Notes: This is the standard security classification for the data. We currently do not allow 'Secret' data to be stored in the Data Ecosystem. This property is NOT case sensitive. +!!! Warning + + This is the standard security classification for the data. We currently do not allow `Secret` data to be stored in the Data Ecosystem. This property is NOT case sensitive. -#### Export classification - Valid values: '0A998'(0 as Zero), 'EAR99', 'Not - Technical Data', 'No License Required' +### Export classification + +Valid values: + +- `0A998`(0 as Zero), +- `EAR99`, +- `Not - Technical Data` (planned) +- `No License Required` (planned) - Notes: We currently only allow data with the ECCN classification 'EAR99' and '0A998'(0 as Zero). This property is NOT case sensitive. +!!! Note + + We currently only allow data with the ECCN classification 'EAR99' and '0A998'(0 as Zero). + This property is NOT case sensitive. -#### Personal data - Valid values: 'Personally Identifiable', 'No Personal Data' +### Personal data + +Valid values: + +- `Personally Identifiable`, +- `No Personal Data` - Notes: We do not currently allow data that is 'Sensitive Personal Information' and this should not be ingested. This property is NOT case sensitive. +!!! Warning "Sensitive Personal Information" + + We do not currently allow data that is _Sensitive Personal Information_ and this should __not__ be ingested. + This property is NOT case sensitive. - [Back to table of contents](#TOC) -## Creating a Record<a name="Creating-a-Record"></a> -This relates to creating Records that are *NOT* derivatives. See the derivative section below for details on Record creation for derivative data. +## Creating a Record + +This relates to creating Records that are __NOT__ derivatives. See the [derivatives](#what-are-derivatives) section below for details on Record creation for derivative data. Once you have a LegalTag created, you can assign it to as many Records as you like. However, it is the data managers' responsibility to assign accurate LegalTags to data. @@ -240,8 +225,7 @@ When creating a Record, the following needs to be assigned for legal compliance: Below is a full example of the payload needed when creating a Record. The *legal* section shows what is required. -<details><summary>Details</summary> - +<details><summary>Example JSON Payload for Creating a LegalTag</summary> ``` [{ "acl": { @@ -265,16 +249,26 @@ Below is a full example of the payload needed when creating a Record. The *legal } }] ``` +</details> -</details> - -* legaltags - This section represents the names of the LegalTag(s) associated with the Record. This has to be supplied when the Record represents raw or source data (i,e, not derivative data) +* legaltags - This section represents the names of the LegalTag(s) associated with the Record. This has to be supplied when the Record represents raw or source data (i,e, not derivative data). * otherRelevantDataCountries - This is the Alpha-2 country codes for the country the data was ingested from and the country where the data is located in Data Ecosystem. otherRelevantDataCountries is not part of the LegalTag(s). It is part of the legal property of the record. The location of the data center, in which the record is stored is automatically added to the otherRelevantDataCountries list when the record is created. This location depends on the environment/region that the partition locates. You can get the list of all valid LegalTags using the Get LegalTags API method. You can use this to help assign only valid LegalTags to data when ingesting. GET /api/legal/v1/legaltags?valid=true +<details><summary>Curl GET legaltags Example</summary> +``` +curl --request GET \ + --url '/api/legal/v1/legaltags?valid=true' \ + --header 'accept: application/json' \ + --header 'authorization: Bearer <JWT>' \ + --header 'content-type: application/json' \ + --header 'data-partition-id: opendes' \ +``` +</details> + <details><summary>Example 200 Response</summary> ``` @@ -317,26 +311,15 @@ You can get the list of all valid LegalTags using the Get LegalTags API method. ... } ``` - </details> -<details><summary>Curl</summary> - -``` -curl --request GET \ - --url '/api/legal/v1/legaltags?valid=true' \ - --header 'accept: application/json' \ - --header 'authorization: Bearer <JWT>' \ - --header 'content-type: application/json' \ - --header 'data-partition-id: opendes' \ -``` +## What are Derivatives? -</details> +In the context of OSDU, the term "derivative data" is data that has been derived from primary data sources. -## What are Derivatives?<a name="What-are-Dervatives"></a> Often when ingesting data into the Data Ecosystem, it is the raw data itself. In these scenarios, you associate a single LegalTag with this data. -However, in the case when the data to be ingested come from multiple sources, it is the case of derivative data. For instance, what if you take multiple Records from the Data Ecosystem and create a whole new Record based on them all? Or what if you run an algorithm over your seismic data and create an attribute associated with this data you want to ingest? +However, in the case when the data to be ingested comes from multiple sources, it is the case of derivative data. For instance, what if you take multiple Records from the Data Ecosystem and create a whole new Record based on them all? Or what if you run an algorithm over your seismic data and create an attribute associated with this data you want to ingest? At this point, you have derivative data (i.e., data derived from data). In these scenarios, you will need to assign LegalTags to this new data which is the union of the LegalTags associated to all the source data from which it was created. @@ -350,7 +333,7 @@ When creating Records that represent derivative data, the following must be assi Below is an example of the minimum number of fields required to ingest a derivative Record. -<details><summary>Details</summary> +<details><summary>Record Example</summary> ``` [{ @@ -379,9 +362,8 @@ Below is an example of the minimum number of fields required to ingest a derivat As shown the parent Records are provided as well as the ORDC of where the derivative was created. The Record service takes responsibility for populating the full LegalTag and ORDC values based on the parents. -[Back to table of contents](#TOC) -## Validating a LegalTag<a name="Validating-a-LegalTag"></a> +## Validating a LegalTag The Storage service validates whether a Record is legally compliant during ingestion and consumption. Therefore, you can delegate the effort to the Record service as the request will fail if the Record is not compliant. @@ -391,8 +373,7 @@ You can validate a LegalTag by using the LegalTag validate API supplying the nam POST /api/legal/v1/legaltags:validate -<details><summary>Curl</summary> - +<details><summary>Curl Post legaltags:validate</summary> ``` curl --request POST \ --url '/api/legal/v1/legaltags:validate' \ @@ -404,25 +385,21 @@ curl --request POST \ "names": ["opendes-demo-legaltag"] }' ``` - </details> If the LegalTag is valid, the response then looks something like this -<details><summary>Details</summary> - +<details><summary>Valid Response Example</summary> ``` { "invalidLegalTags": [] } ``` - -</details> +</details> If the LegalTag is invalid, the response then looks something like this -<details><summary>Details</summary> - +<details><summary>Invalid Response Example</summary> ``` { "invalidLegalTags": [ @@ -430,21 +407,21 @@ If the LegalTag is invalid, the response then looks something like this ] } ``` - </details> So if you just want to check that the given LegalTag(s) are currently valid, you only have to check if the returned 'invalidLegalTags' collection is empty. Ingestion services forward the request to the LegalTag API using the same _SAuth_ token making the ingestion request. This checks both that a LegalTag exists and that the data has appropriate access to it. -## Updating a LegalTag<a name="Updating-a-LegalTag"></a> +## Updating a LegalTag + One of the main cases where a LegalTag can become invalid is if a contract expiration date passes. This makes both the LegalTag invalid and *all* data associated with that LegalTag including derivatives. In these situations we can update LegalTags to make them valid again and so make the associated data accessible. Currently we only allow the update of the *description*, *contract ID*, *expiration date* and *extensionProperties* properties. PUT /api/legal/v1/legaltags -<details><summary>Curl</summary> +<details><summary>Curl Put legaltags Example</summary> ``` curl --request PUT \ @@ -466,32 +443,36 @@ curl --request PUT \ </details> +!!! Danger "Valid and Validate" + + There is a difference between "querying for valid or invalid LegalTag (List Legal Tag API )" and "checking if the legalTag is Valid (Validate Legal Tag API)". -*Note: There is a difference between "querying for valid or invalid LegalTag (List Legal Tag API )" and "checking if the legalTag is Valid (Validate Legal Tag API)". The "List Legal tag" will check whether the tag is valid right now and "Validate Legal Tag" will check whether the tag would be valid after the next once-a-day update process. -Therefore, updating the LegalTag Expiration Date will not make the record visible as valid in the Valid Tag List until its status has been updated. +Therefore, updating the LegalTag _Expiration Date_ will not make the record visible as valid in the Valid Tag List until its status has been updated. The Update process validates the LegalTags and then updates the status of the LegalTag from Invalid to Valid or vice versa. This update process runs only once in a day. -[Back to table of contents](#TOC) - -## Compliance on consumption<a name="Compliance-on-Consumption"></a> +## Compliance on Consumption As previously stated, the Records in the Storage service largely governs data compliance. This means that if you use the Storage or Search core services, then compliance on consumption is handled on your behalf i.e. these services will not return Records that are no longer legally compliant. However, there are use cases where you may not use these services all the time e.g. if you have your own operational data store. In these cases you will need to check the LegalTags associated with your data are still valid before allowing consumption. For this, we have a PubSub topic that can be subscribed to. -*NOTE This topic can only be subscribed to if you deploy your service within the Data Ecosystem Google Project. +!!! Warning "Compliance on Consumption" + + Currently, this topic can only be subscribed to if you deploy your service within the Data Ecosystem Google Project. This topic has the form +`projects/{googleProjectId}/topics/legaltags_changed` - projects/{googleProjectId}/topics/legaltags_changed - This means you need to make a subscription to every data partition project you wish to receive the notifications on. -*NOTE: When new data partitions are added into the Data Ecosystem, it may take up to 24 hours for the topic to become available to subscribe to.* +!!! Info "Async Process" + + When new data partitions are added into the Data Ecosystem, it may take up to 24 hours for the topic to become available to subscribe to. For more information on subscribing to PubSub topics, please use the Google documentation [here](https://cloud.google.com/pubsub/docs/subscriber). -## The LegalTag Changed notification<a name="The-LegalTag-Changed-notification"></a> +## The LegalTag Changed Notification + After subscribing to the topic, you will receive notifications daily. These notifications will list all LegalTags that have changed, and whether the LegalTag has become compliant or non-compliant. <details><summary>Details</summary> @@ -516,7 +497,7 @@ If it has become incompliant, you must make sure associated data is no longer al If it is marked compliant, data that was not allowed for consumption can now be consumed through your services. -## The LegalTag About to Expire notification<a name="The-LegalTag-AboutToExpire-notification"></a> +## The LegalTag About to Expire notification After subscribing to the topic, you will receive notifications daily. These notifications will list all LegalTags that will expire soon. The definition of "soon" is configurable by the deployer. @@ -528,7 +509,7 @@ This topic has the form Note: this feature is also behind a feature flag, meaning the deployer must specifically enable the feature. -<details><summary>Details</summary> +<details><summary>Expiration Details</summary> ``` { @@ -547,15 +528,14 @@ must specifically enable the feature. Time to expire should be configurable on deployment level. We should be able to provide list of all time periods before LegalTag expires. This will be achieved by introducing new environment variable `legaltag.expirationAlerts`. Format is comma separated values which consists of number and letter suffix, for example: 3d,2w,1m Where suffix letter should represent this time periods: -* #d - # number of days -* #w - # number of weeks -* #m - # number of months - -[Back to table of contents](#TOC) +* \#d - \# number of days +* \#w - \# number of weeks +* \#m - \# number of months ## Version info endpoint For deployment available public `/info` endpoint, which provides build and git related information. -#### Example response: + +Example info response: ```json { "groupId": "org.opengroup.osdu", @@ -584,7 +564,198 @@ For deployment available public `/info` endpoint, which provides build and git r 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) \ No newline at end of file +## Legal Query + +!!! Warning "New in M23" + + The `/legaltags:query` API was introduced in M23. This API can also be disabled server side via a [feature flag](/features/#legal-query-api) (`featureFlag.legalTagQueryApi.enabled`). If it is disabled you will receive a HTTP 405 Method Not Allowed. + +This new API `POST /legaltags:query` takes a payload of `{"queryList": ["attribute=value"]}` and is implemented using contains (as partial match ignoring case). + +This query like `/legaltags` API takes a parameter `valid` (boolean). +If `valid` parameter is true returns only valid LegalTags, if false returns only invalid LegalTags. +Default value is true for `valid` parameter. + +### Query parameters + +- queryList - A list of query strings. Currently multiple queries are supported. All values are treated as strings except for dates when using `between (start_date, end_date)` format. All queries are implemented as a case-insensitive iscontains, using `*` or `?` is not supported. + +!!! Note "Boolean Values" + + If an attribute has a Boolean value querying on that attribute is supported and will match as string or boolean value. + +### Expiration Date + +For `expirationDate` you can do a string match (even partial) using `{"queryList": ["expirationDate=value"]}` or given a date range using `between`: + +``` +{"queryList": ["expirationDate between (2023-01-01, 2024-12-31)"]} +``` + +When using `between` the dates provided will not be included in query results. What you are searching for must be `between` the two dates (i.e. the above will provide results from 2023-01-02 thru 2024-12-30). + +This also supports all attributes, including those in extensionProperties. All extension properties are based upon string matching only. This will even work on nested properties for example: + +``` + "extensionProperties": { + "AgreementIdentifier": "dz-test-O", + "EffectiveDate": "2023-01-00T00:00:00", + "TerminationDate": "2099-12-31T00:00:00", + "AffiliateEnablementIndicator": true, + "AgreementParties": [ + { + "AgreementPartyType": "EnabledAffiliate", + "AgreementParty": "Acme RDS" + } + ] + } +``` + +Example search by name: + +``` +curl -X 'POST' \ + 'https://site/api/legal/v1/legaltags:query?valid=true' \ + -H 'accept: */*' \ + -H 'data-partition-id: osdu' \ + -H 'Content-Type: application/json' \ + -d '{"queryList":["name=test"]}' +``` + +This will return the same kind of response getall tags would do (but just the matching tags): +<details><summary>Response example</summary> +``` +{ + "legalTags": [ + { + "name": "osdu-1703151379194", + "description": "test for osdu-1703151379194", + "properties": { + "countryOfOrigin": [ + "US" + ], + "contractId": "A1234", + "expirationDate": "2099-01-25", + "originator": "MyCompany", + "dataType": "Public Domain Data", + "securityClassification": "Public", + "personalData": "No Personal Data", + "exportClassification": "EAR99" + } + } +} +``` +</details> + +### Operators +The `operatorList` provides a way to how to treat multiple queries. Currently **only** a single operator is supported. + +- `union` - the default (if no operatorList provided), return the set of all tags from all queries removing the duplicates. +- `intersection` - return the common set of tags that are found in each of the queries. +- `add` - return the set of all tags from all queries keeping any duplicates. + +!!! Warning "Intersection Requires more than one query" + + Intersection operator is only intended to be used with multiple queries. An intersection of a query with itself (a single query in the queryList) is an empty set (ðœ™) and the result will always be an empty match. + +<details><summary>Operator Payload example</summary> + +``` +{ + "queryList": ["expirationDate=value"] + "operatorList": ["union"] +} +``` +</details> + +### Planned support for the following + +- operatorList - additional operators to control how to join multiple queries together (logical operators) +- sortBy - Allows you to add one or more sorts on specific fields +- sortOrder - ascending or descending +- limit - The maximum number of results to return + +<details><summary>Payload example</summary> +``` +{ + "queryList": ["name=test"], + "operatorList": ["union"], + "sortBy": "name", + "sortOrder": "ascending", + "limit": 10 +} +``` +</details> + +### Extension Properties +Extension properties can be searched just like any other legal tag property. For example: +`["AgreementIdentifier=search string"]` + +### Free Text Query + +Free text search is currently supported as part of M23. Free text query is implemented as a case-insensitive iscontains, using `*` or `?` is not supported. + +* `"queryList":["string to search for"]` +* `"queryList":["any=string to search for"]` + +Free text search will check the values of the following properties: + +* name, +* description, +* contractId, +* originator, +* countryOfOrigin, +* extension properties + +If [feature flag](/features/#legal-query-api-free-text) `featureFlag.legalTagQueryApiFreeTextAllFields.enabled` is enabled then it will also check these additional properties for a match: + +* expirationDate, +* dataType, +* securityClassification, +* personalData, +* exportClassification + +### Additional Examples + +<details><summary>Curl Post legaltags Query Examples</summary> + +``` +curl -X 'POST' \ + 'https://site/api/legal/v1/legaltags:query?valid=true' \ + -H 'accept: */*' \ + -H 'data-partition-id: osdu' \ + -H 'Content-Type: application/json' \ + -d '{"queryList":["AgreementIdentifier=test"]}' +``` + +``` +curl -X 'POST' \ + 'https://site/api/legal/v1/legaltags:query?valid=true' \ + -H 'accept: */*' \ + -H 'data-partition-id: osdu' \ + -H 'Content-Type: application/json' \ + -d '{"queryList":["description=foo"]}' +``` + +``` +curl -X 'POST' \ + 'https://site/api/legal/v1/legaltags:query?valid=true' \ + -H 'accept: */*' \ + -H 'data-partition-id: osdu' \ + -H 'Content-Type: application/json' \ + -d '{"queryList":["countryOfOrigin=US"]}' +``` + +``` +curl -X 'POST' \ + 'https://site/api/legal/v1/legaltags:query?valid=true' \ + -H 'accept: */*' \ + -H 'data-partition-id: osdu' \ + -H 'Content-Type: application/json' \ + -d '{"queryList":["AffiliateEnablementIndicator=True"]}' +``` +</details> \ No newline at end of file diff --git a/docs/docs/features.md b/docs/docs/features.md new file mode 100644 index 0000000000000000000000000000000000000000..6d26a226fbf57d7dd7a786e027004f4c651e9b46 --- /dev/null +++ b/docs/docs/features.md @@ -0,0 +1,20 @@ +# Feature Flags + +In M23 three feature flags were added to legal service to optionally control the behavior: + +## Expire Legal Tag +`featureFlag.aboutToExpireLegalTag.enabled` see [about to expire](/api#the-legaltag-about-to-expire-notification) for more details. + +## Legal Query API +The feature flag `featureFlag.legalTagQueryApi.enabled` enables or disables this [API](api.md#legal-query). + +## Legal Query API Free Text +The feature flag `featureFlag.legalTagQueryApiFreeTextAllFields.enabled` when disabled excludes the following from the query match: + +- expirationDate, +- dataType, +- securityClassification, +- personalData, +- exportClassification + +See [Legal Query Free Text](api.md#free-text-query) for more details. \ No newline at end of file diff --git a/docs/tutorial/images/PartitionDataContainer.png b/docs/docs/images/PartitionDataContainer.png similarity index 100% rename from docs/tutorial/images/PartitionDataContainer.png rename to docs/docs/images/PartitionDataContainer.png diff --git a/docs/tutorial/images/UploadFile.png b/docs/docs/images/UploadFile.png similarity index 100% rename from docs/tutorial/images/UploadFile.png rename to docs/docs/images/UploadFile.png diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 0000000000000000000000000000000000000000..b729c5242049c68aecab9f702870a80dfe7e3c4c --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,30 @@ +# OSDU Legal (Compliance) Service + +## Introduction + +This document covers how to remain compliant at the different stages of the data lifecycle inside the Data Ecosystem. + +1. When ingesting data +2. Whilst the data is inside the Data Ecosystem +3. When consuming data + +The clients' interaction revolves around ingestion and consumption, so this is when you need to use what is contained in this guide. Point 2 should be mostly handled on the clients’ behalf; however, it is still important to understand that this is happening as it has ramifications on when and how data can be consumed. + +Data compliance is largely governed through the Records in the storage service. Though there is an independent legal service and LegalTags entity, these offer no compliance by themselves. + +Records have a Legal section in their schema and this is where the compliance is enforced. However, clients must still make sure they are using the Record service correctly to remain compliant. + +Further details can be found in the [Creating a Record](./api.md#creating-a-record) section. + +## What is a LegalTag? +A LegalTag is the entity that represents the legal status of data in the Data Ecosystem. It is a collection of *properties* that governs how the data can be consumed and ingested. + +A legal tag is required for data ingestion. Therefore, creation of a legal tag is a necessary first step if there isn't a legal tag already exists for use with the ingested data. The LegalTag name needs to be assigned to the LegalTag during creation, and is used for reference. The name is the unique identifier for the LegalTag that is used to access it. + +When data is ingested, it is assigned the LegalTag *name*. This name is checked for a corresponding valid LegalTag in the system. A valid LegalTag means it exists and has not expired. If a LegalTag is invalid, the data is rejected. For instance, we may not allow ingestion of data from a certain country, or we may not allow consumption of data that has an expired contract. + +In the same manner, the ingested data will be invalidated (soft-deleted) when the legal tag expires, as it would no longer be compliant. + +Upon ingestion of data, it's the responsiblity of the process ingesting that data to use the appropriate tag for business rules and processes.. + +Further details can be found in the [Creating a Legal Tag](./api.md#creating-a-legaltag). diff --git a/docs/docs/ingestion.md b/docs/docs/ingestion.md new file mode 100644 index 0000000000000000000000000000000000000000..97dde7866e83a1968454ec65c2f3d7656ede47b0 --- /dev/null +++ b/docs/docs/ingestion.md @@ -0,0 +1,9 @@ +# Ingestion Workflow + + + +The above diagram shows the typical sequence of events of a data ingestion. The important points to highlight are as follow: + +* It is the ingestor's responsibility to create a LegalTag. LegalTag validation happens at this point. +* The Storage service validates the LegalTag for the data being ingested. +* Only after validating a LegalTag exists can we ingest data. No data should be stored at any point in the Data Ecosystem that does not have a valid LegalTag. \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000000000000000000000000000000000000..a522b698c816942b7562a9a3c35df6b1806463fa --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,45 @@ +site_name: OSDU Legal Service Documentation +site_description: OSDU Legal Service +site_author: Shane Hutchins + +# Repository +repo_url: https://community.opengroup.org/osdu/platform/security-and-compliance/legal +repo_name: legal + +# Copyright +copyright: Copyright © 2024 Open Subsurface Data Universe Software + +theme: + name: material + palette: + - scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + font: + text: Roboto + code: Roboto Mono + +markdown_extensions: + - admonition + - sane_lists + +plugins: + - search + - git-revision-date + +nav: + - OSDU Legal (Compliance) Service: 'index.md' + - Ingestion Workflow: 'ingestion.md' + - API: 'api.md' + - Configuration: + - Allow Data Ingestion from Specific Country of Origin: 'AllowDataIngestionFromCertainCOO.md' + - Feature Flags: 'features.md' diff --git a/docs/tutorial/.gitkeep b/docs/tutorial/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/docs/tutorial/AllowDataIngestionFromCertainCOO.md b/docs/tutorial/AllowDataIngestionFromCertainCOO.md deleted file mode 100644 index 8ca4190d89714ac9ed2d4e59d535de5f2754e0bf..0000000000000000000000000000000000000000 --- a/docs/tutorial/AllowDataIngestionFromCertainCOO.md +++ /dev/null @@ -1,29 +0,0 @@ -We can allow certain country as a valid COO(Country of Origin) by uploading customized legal configuration file into cloud storage. It will override the default configuration for legal service. - -Following steps can be taken to allow a specific COO: - -1. Refer to following example and create a `Legal_COO.json` file: -``` -[{ - "name": "Brazil", - "alpha2": "BR", - "numeric": 76, - "residencyRisk": "Client consent required", - "typesNotApplyDataResidency": ["Transferred Data"] -}] -``` - -To obtain the proper alpha, numeric code, and data types without applying data residency constraints (`alpha2`, `numeric`, `typesNotApplyDataResidency` fields), please refer to the [Legal Service Default Configuration](https://community.opengroup.org/osdu/platform/security-and-compliance/legal/-/blob/master/legal-core/src/main/resources/DefaultCountryCode.json). -then enter the appropriate restriction level in the `residencyRisk` field. - -__NOTE:__ Usually we set `residencyRisk` to `Client consent required` when we are allowing data from this country as an exception, but please consult with business if this is the correct value here. - -2. Go to cloud storage location where we store legal config file, taking `Azure resource group - dev dp1` as an example: - - <br/> - -4. Go to the `legal-service-azure-configuration` container and upload the `Legal_COO.json` file created in step 1, replace the existing item with the same name if existed: - - <br/> - - __NOTE:__ Please make sure the file name is exactly the same with the existing file (`Legal_COO.json`), it's case-sensitive. \ No newline at end of file diff --git a/legal-core-plus/src/main/resources/application.properties b/legal-core-plus/src/main/resources/application.properties index 42939046c4cee6b0ef3c6032f0258b255500832c..182264b20e4bf6c0022394785a5b6b5fedd427a2 100644 --- a/legal-core-plus/src/main/resources/application.properties +++ b/legal-core-plus/src/main/resources/application.properties @@ -21,7 +21,8 @@ server.servlet.contextPath=/api/legal/v1/ # Log config LOG_PREFIX=legal -logging.level.org.springframework.web=${LOG_LEVEL:DEBUG} +logging.level.org.springframework.web=${LOG_LEVEL:INFO} +logging.level.org.opengroup.osdu=${LOG_LEVEL:INFO} # JVM config JAVA_HEAP_OPTS=-Xms4096M -Xmx4096M @@ -48,4 +49,5 @@ partition-auth-enabled=false # Feature flag settings featureFlag.strategy=appProperty -featureFlag.aboutToExpireLegalTag.enabled=false \ No newline at end of file +featureFlag.aboutToExpireLegalTag.enabled=false +featureFlag.legalTagQueryApi.enabled=true \ No newline at end of file diff --git a/legal-core/pom.xml b/legal-core/pom.xml index bd67ffe2dd00765faee7f20397463165d07e90ea..5fa87826af471e5a510fdacc7b45909d3c36d3e7 100644 --- a/legal-core/pom.xml +++ b/legal-core/pom.xml @@ -114,6 +114,12 @@ <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> + <exclusions> + <exclusion> + <groupId>com.vaadin.external.google</groupId> + <artifactId>android-json</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> @@ -174,6 +180,12 @@ <version>1.2.0</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.json</groupId> + <artifactId>json</artifactId> + <version>20230618</version> + </dependency> + </dependencies> <build> diff --git a/legal-core/src/main/java/org/opengroup/osdu/legal/Constants.java b/legal-core/src/main/java/org/opengroup/osdu/legal/Constants.java index 118d55528f7ddf5053aab2c1db572161fedb7174..c17d2a4d1e0a6f374e84bce75cf93981f17daf8e 100644 --- a/legal-core/src/main/java/org/opengroup/osdu/legal/Constants.java +++ b/legal-core/src/main/java/org/opengroup/osdu/legal/Constants.java @@ -2,4 +2,17 @@ package org.opengroup.osdu.legal; public class Constants { public static final String ABOUT_TO_EXPIRE_FEATURE_NAME = "featureFlag.aboutToExpireLegalTag.enabled"; + + public static final String LEGAL_QUERY_API_FEATURE_NAME = "featureFlag.legalTagQueryApi.enabled"; + + public static final String LEGAL_QUERY_API_FREE_TEXT_ALL_FIELDS_FEATURE_NAME = "featureFlag.legalTagQueryApiFreeTextAllFields.enabled"; + + public static final String LEGAL_QUERY_API_BETWEEN_START = "("; + public static final String LEGAL_QUERY_API_BETWEEN_END = ")"; + public static final String LEGAL_QUERY_API_ATTRIBUTE_SEPARATOR = "="; + public static final String LEGAL_QUERY_API_QUERY_SEPARATOR = ","; + public static final String LEGAL_QUERY_API_FREE_TEXT_ATTRIBUTE = "any"; + public static final String LEGAL_QUERY_API_UNION_OPERATOR = "union"; + public static final String LEGAL_QUERY_API_INTERSECTION_OPERATOR = "intersection"; + public static final String LEGAL_QUERY_API_ADD_OPERATOR = "add"; } diff --git a/legal-core/src/main/java/org/opengroup/osdu/legal/FeatureFlagController.java b/legal-core/src/main/java/org/opengroup/osdu/legal/FeatureFlagController.java index cc7b119a76545e19831f436c2ba196a5e67b6d65..7a0fb1dc235d8d194e01c0fc9e2c44e369ffb0fc 100644 --- a/legal-core/src/main/java/org/opengroup/osdu/legal/FeatureFlagController.java +++ b/legal-core/src/main/java/org/opengroup/osdu/legal/FeatureFlagController.java @@ -10,7 +10,21 @@ public class FeatureFlagController { @Autowired private IFeatureFlag aboutToExpireLegalTagFeatureFlag; + @Autowired + private IFeatureFlag legalTagQueryApiFeatureFlag; + + @Autowired + private IFeatureFlag legalTagQueryApiFreeTextAllFieldsFeatureFlag; + public Boolean isAboutToExpireFeatureFlagEnabled() { return aboutToExpireLegalTagFeatureFlag.isFeatureEnabled(Constants.ABOUT_TO_EXPIRE_FEATURE_NAME); } + + public Boolean isLegalTagQueryApiFlagEnabled() { + return legalTagQueryApiFeatureFlag.isFeatureEnabled(Constants.LEGAL_QUERY_API_FEATURE_NAME); + } + + public Boolean isLegalTagQueryApiFreeTextAllFieldsFlagEnabled() { + return legalTagQueryApiFreeTextAllFieldsFeatureFlag.isFeatureEnabled(Constants.LEGAL_QUERY_API_FREE_TEXT_ALL_FIELDS_FEATURE_NAME); + } } diff --git a/legal-core/src/main/java/org/opengroup/osdu/legal/api/LegalTagApi.java b/legal-core/src/main/java/org/opengroup/osdu/legal/api/LegalTagApi.java index 279ec0d1c9569fc1cf2769a723b9c93bf07afcbe..a2dcf75efb7aa2814f240757d95b7b35c1487817 100644 --- a/legal-core/src/main/java/org/opengroup/osdu/legal/api/LegalTagApi.java +++ b/legal-core/src/main/java/org/opengroup/osdu/legal/api/LegalTagApi.java @@ -12,12 +12,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.opengroup.osdu.core.common.model.http.AppError; import org.opengroup.osdu.core.common.model.legal.ServiceConfig; import org.opengroup.osdu.core.common.model.legal.validation.ValidName; -import org.opengroup.osdu.legal.tags.dto.InvalidTagsWithReason; -import org.opengroup.osdu.legal.tags.dto.LegalTagDto; -import org.opengroup.osdu.legal.tags.dto.LegalTagDtos; -import org.opengroup.osdu.legal.tags.dto.ReadablePropertyValues; -import org.opengroup.osdu.legal.tags.dto.RequestLegalTags; -import org.opengroup.osdu.legal.tags.dto.UpdateLegalTag; +import org.opengroup.osdu.legal.tags.dto.*; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -169,4 +164,24 @@ public interface LegalTagApi { @PreAuthorize("@authorizationFilter.hasPermission('" + ServiceConfig.LEGAL_USER + "', '" + ServiceConfig.LEGAL_EDITOR + "', '" + ServiceConfig.LEGAL_ADMIN + "')") @GetMapping("/legaltags:properties") ResponseEntity<ReadablePropertyValues> getLegalTagProperties(); + + @Operation(summary = "${legalTagApi.queryLegalTag.summary}", description = "${legalTagApi.queryLegalTag.description}", + security = {@SecurityRequirement(name = "Authorization")}, tags = {"legaltag"}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Retrieved LegalTags successfully.", content = {@Content(schema = @Schema(implementation = LegalTagDtos.class))}), + @ApiResponse(responseCode = "400", description = "Bad Request", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "403", description = "User not authorized to perform the action.", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "404", description = "Requested LegalTag to update was not found.", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "405", description = "Method not allowed. Legal Query API is disabled.", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "500", description = "Internal Server Error", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "502", description = "Bad Gateway", content = {@Content(schema = @Schema(implementation = AppError.class))}), + @ApiResponse(responseCode = "503", description = "Service Unavailable", content = {@Content(schema = @Schema(implementation = AppError.class))}) + }) + @PreAuthorize("@authorizationFilter.hasPermission('" + ServiceConfig.LEGAL_USER + "', '" + ServiceConfig.LEGAL_EDITOR + "', '" + ServiceConfig.LEGAL_ADMIN + "')") + @PostMapping("/legaltags:query") + ResponseEntity<LegalTagDtos> queryLegalTag(@Valid @NotNull @RequestBody QueryLegalTag searchInput, @Parameter(description = "If true returns only valid LegalTags, if false returns only invalid LegalTags. Default value is true.") + @RequestParam(name = "valid", required = false, defaultValue = "true") boolean valid); + + } diff --git a/legal-core/src/main/java/org/opengroup/osdu/legal/controller/LegalTagController.java b/legal-core/src/main/java/org/opengroup/osdu/legal/controller/LegalTagController.java index 26b418760ea64f191b975f45d710861a476590de..883c66a2b7d524f73c047a8a65a5ab97cac5d236 100644 --- a/legal-core/src/main/java/org/opengroup/osdu/legal/controller/LegalTagController.java +++ b/legal-core/src/main/java/org/opengroup/osdu/legal/controller/LegalTagController.java @@ -1,130 +1,137 @@ package org.opengroup.osdu.legal.controller; import com.google.gson.Gson; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import jakarta.inject.Inject; +import jakarta.validation.ValidationException; + +import org.opengroup.osdu.core.common.model.http.AppException; import org.opengroup.osdu.core.common.model.http.RequestInfo; import org.opengroup.osdu.core.common.model.legal.AllowedLegaltagPropertyValues; import org.opengroup.osdu.legal.api.LegalTagApi; import org.opengroup.osdu.legal.countries.LegalTagCountriesService; import org.opengroup.osdu.legal.logging.AuditLogger; import org.opengroup.osdu.legal.tags.LegalTagService; -import org.opengroup.osdu.legal.tags.dto.InvalidTagsWithReason; -import org.opengroup.osdu.legal.tags.dto.LegalTagDto; -import org.opengroup.osdu.legal.tags.dto.LegalTagDtos; -import org.opengroup.osdu.legal.tags.dto.ReadablePropertyValues; -import org.opengroup.osdu.legal.tags.dto.RequestLegalTags; -import org.opengroup.osdu.legal.tags.dto.UpdateLegalTag; +import org.opengroup.osdu.legal.tags.dto.*; +import org.opengroup.osdu.legal.FeatureFlagController; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RestController; -import jakarta.inject.Inject; -import jakarta.validation.ValidationException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - @RestController public class LegalTagController implements LegalTagApi { - private RequestInfo requestInfo; - private LegalTagService legalTagService; - @Inject - private AllowedLegaltagPropertyValues allowedLegaltagPropertyValues; - @Inject - private LegalTagCountriesService legalTagCountriesService; - @Inject - private AuditLogger auditLogger; - - @Inject //injectMock only works on setter DI - public void setRequestInfo(RequestInfo requestInfo) - { - this.requestInfo = requestInfo; + private RequestInfo requestInfo; + private LegalTagService legalTagService; + @Inject private AllowedLegaltagPropertyValues allowedLegaltagPropertyValues; + @Inject private LegalTagCountriesService legalTagCountriesService; + @Inject private AuditLogger auditLogger; + @Inject private FeatureFlagController featureFlagController; + + @Inject // injectMock only works on setter DI + public void setRequestInfo(RequestInfo requestInfo) { + this.requestInfo = requestInfo; + } + + @Inject // injectMock only works on setter DI + public void setLegalTagService(LegalTagService legalTagService) { + this.legalTagService = legalTagService; + } + + @Override + public ResponseEntity<LegalTagDto> createLegalTag(LegalTagDto legalTag) { + LegalTagDto output = legalTagService.create(legalTag, requestInfo.getTenantInfo().getName()); + return new ResponseEntity<>(output, HttpStatus.CREATED); + } + + @Override + public ResponseEntity<LegalTagDto> updateLegalTag(UpdateLegalTag legalTag) { + LegalTagDto output = legalTagService.update(legalTag, requestInfo.getTenantInfo().getName()); + return new ResponseEntity<>(output, HttpStatus.OK); + } + + @Override + public ResponseEntity<LegalTagDtos> listLegalTags(boolean valid) { + if (requestInfo.getTenantInfo() == null) { + throw new ValidationException("No tenant supplied"); } + LegalTagDtos output = legalTagService.list(valid, requestInfo.getTenantInfo().getName()); + return new ResponseEntity<>(output, HttpStatus.OK); + } - @Inject //injectMock only works on setter DI - public void setLegalTagService(LegalTagService legalTagService) - { - this.legalTagService = legalTagService; - } + @Override + public ResponseEntity getLegalTag(String name) { + LegalTagDto output = legalTagService.get(name, requestInfo.getTenantInfo().getName()); - @Override - public ResponseEntity<LegalTagDto> createLegalTag(LegalTagDto legalTag) { - LegalTagDto output = legalTagService.create(legalTag, requestInfo.getTenantInfo().getName()); - return new ResponseEntity<>(output, HttpStatus.CREATED); + if (output == null) return new ResponseEntity<>(createNotFoundBody(), HttpStatus.NOT_FOUND); + else { + return new ResponseEntity<>(output, HttpStatus.OK); } - - @Override - public ResponseEntity<LegalTagDto> updateLegalTag(UpdateLegalTag legalTag) { - LegalTagDto output = legalTagService.update(legalTag, requestInfo.getTenantInfo().getName()); - return new ResponseEntity<>(output, HttpStatus.OK); + } + + @Override + public ResponseEntity<HttpStatus> deleteLegalTag(String name) { + if (legalTagService.delete( + requestInfo.getTenantInfo().getProjectId(), + name, + requestInfo.getHeaders(), + requestInfo.getTenantInfo().getName())) return new ResponseEntity<>(HttpStatus.NO_CONTENT); + else return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + + @Override + public ResponseEntity<LegalTagDtos> getLegalTags(RequestLegalTags requestedTags) { + String[] names = requestedTags.getNames().stream().toArray(String[]::new); + LegalTagDtos result = legalTagService.getBatch(names, requestInfo.getTenantInfo().getName()); + + if (result == null || result.getLegalTags().size() != requestedTags.getNames().size()) { + return new ResponseEntity<>(result, HttpStatus.NOT_FOUND); + } else { + return new ResponseEntity<>(result, HttpStatus.OK); } - - @Override - public ResponseEntity<LegalTagDtos> listLegalTags(boolean valid) { - if (requestInfo.getTenantInfo() == null) { - throw new ValidationException("No tenant supplied"); - } - LegalTagDtos output = legalTagService.list(valid, requestInfo.getTenantInfo().getName()); - return new ResponseEntity<>(output, HttpStatus.OK); + } + + @Override + public ResponseEntity<InvalidTagsWithReason> validateLegalTags(RequestLegalTags requestedTags) { + InvalidTagsWithReason result = + legalTagService.validate( + requestedTags.getNames().toArray(new String[0]), requestInfo.getTenantInfo().getName()); + + return new ResponseEntity<>(result, HttpStatus.OK); + } + + @Override + public ResponseEntity<ReadablePropertyValues> getLegalTagProperties() { + ReadablePropertyValues output = new ReadablePropertyValues(); + output.setCountriesOfOrigin(legalTagCountriesService.getValidCOOs()); + output.setOtherRelevantDataCountries(legalTagCountriesService.getValidORDCs()); + output.setExportClassificationControlNumbers(allowedLegaltagPropertyValues.getEccns()); + output.setPersonalDataTypes(allowedLegaltagPropertyValues.getPersonalDataType()); + output.setSecurityClassifications(allowedLegaltagPropertyValues.getSecurityClassifications()); + output.setDataTypes(allowedLegaltagPropertyValues.getDataTypes()); + auditLogger.readLegalPropertiesSuccess(Collections.singletonList(output.toString())); + + return new ResponseEntity<>(output, HttpStatus.OK); + } + + @Override + public ResponseEntity<LegalTagDtos> queryLegalTag(QueryLegalTag searchInput, boolean valid) { + if (requestInfo.getTenantInfo() == null) { + throw new ValidationException("No tenant supplied"); } - - @Override - public ResponseEntity getLegalTag(String name) { - LegalTagDto output = legalTagService.get(name, requestInfo.getTenantInfo().getName()); - - if (output == null) - return new ResponseEntity<>(createNotFoundBody(), HttpStatus.NOT_FOUND); - else { - return new ResponseEntity<>(output, HttpStatus.OK); - } - } - - @Override - public ResponseEntity<HttpStatus> deleteLegalTag(String name) { - if (legalTagService.delete(requestInfo.getTenantInfo().getProjectId(), name, requestInfo.getHeaders(), - requestInfo.getTenantInfo().getName())) - return new ResponseEntity<>(HttpStatus.NO_CONTENT); - else - return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); - } - - @Override - public ResponseEntity<LegalTagDtos> getLegalTags(RequestLegalTags requestedTags) { - String[] names = requestedTags.getNames().stream().toArray(String[]::new); - LegalTagDtos result = legalTagService.getBatch(names, requestInfo.getTenantInfo().getName()); - - if (result == null || result.getLegalTags().size() != requestedTags.getNames().size()) { - return new ResponseEntity<>(result, HttpStatus.NOT_FOUND); - } else { - return new ResponseEntity<>(result, HttpStatus.OK); - } - } - - @Override - public ResponseEntity<InvalidTagsWithReason> validateLegalTags(RequestLegalTags requestedTags) { - InvalidTagsWithReason result = legalTagService.validate(requestedTags.getNames().toArray(new String[0]), - requestInfo.getTenantInfo().getName()); - - return new ResponseEntity<>(result, HttpStatus.OK); - } - - @Override - public ResponseEntity<ReadablePropertyValues> getLegalTagProperties() { - ReadablePropertyValues output = new ReadablePropertyValues(); - output.setCountriesOfOrigin(legalTagCountriesService.getValidCOOs()); - output.setOtherRelevantDataCountries(legalTagCountriesService.getValidORDCs()); - output.setExportClassificationControlNumbers(allowedLegaltagPropertyValues.getEccns()); - output.setPersonalDataTypes(allowedLegaltagPropertyValues.getPersonalDataType()); - output.setSecurityClassifications(allowedLegaltagPropertyValues.getSecurityClassifications()); - output.setDataTypes(allowedLegaltagPropertyValues.getDataTypes()); - auditLogger.readLegalPropertiesSuccess(Collections.singletonList(output.toString())); - - return new ResponseEntity<>(output, HttpStatus.OK); - } - - private String createNotFoundBody() { - final Map<String, String> body = new HashMap<>(); - body.put("error", "Not found."); - return new Gson().toJson(body); + if (Boolean.FALSE.equals(featureFlagController.isLegalTagQueryApiFlagEnabled())) { + return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED); } + LegalTagDtos output = + legalTagService.queryLegalTag(searchInput, true, requestInfo.getTenantInfo().getName()); + return new ResponseEntity<>(output, HttpStatus.OK); + } + + private String createNotFoundBody() { + final Map<String, String> body = new HashMap<>(); + body.put("error", "Not found."); + return new Gson().toJson(body); + } } diff --git a/legal-core/src/main/java/org/opengroup/osdu/legal/countries/LegalTagCountriesTenantRepositories.java b/legal-core/src/main/java/org/opengroup/osdu/legal/countries/LegalTagCountriesTenantRepositories.java index d846df979f90837fdeb038044af6f7ad89d97799..8979205accb00baae68fb8bf371592374ae15e43 100644 --- a/legal-core/src/main/java/org/opengroup/osdu/legal/countries/LegalTagCountriesTenantRepositories.java +++ b/legal-core/src/main/java/org/opengroup/osdu/legal/countries/LegalTagCountriesTenantRepositories.java @@ -41,7 +41,7 @@ public class LegalTagCountriesTenantRepositories { } private AppException invalidTenantGivenException(String tenantName){ - log.warning(String.format("Requested tenantname does not exist in list of tenants %s", tenantName)); + log.warning(String.format("Requested tenantname '%s' does not exist in list of tenants", tenantName)); return new AppException(403, "Forbidden", String.format("You do not have access to the %s, value given %s", DpsHeaders.DATA_PARTITION_ID, tenantName)); } diff --git a/legal-core/src/main/java/org/opengroup/osdu/legal/tags/LegalTagService.java b/legal-core/src/main/java/org/opengroup/osdu/legal/tags/LegalTagService.java index f029028ee9c7d6b0b326d378544985cb8de87574..17b3e1e77e1dbd9c66e05211e7d62ac91df7c9f8 100644 --- a/legal-core/src/main/java/org/opengroup/osdu/legal/tags/LegalTagService.java +++ b/legal-core/src/main/java/org/opengroup/osdu/legal/tags/LegalTagService.java @@ -1,250 +1,612 @@ package org.opengroup.osdu.legal.tags; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +import org.apache.http.HttpStatus; + import com.google.common.base.Strings; import com.google.common.collect.Iterables; -import org.opengroup.osdu.core.common.model.http.DpsHeaders; -import org.opengroup.osdu.legal.logging.AuditLogger; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.util.*; +import java.util.logging.ConsoleHandler; +import java.util.regex.Matcher; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import jakarta.inject.Inject; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.ObjectUtils.Null; +import org.json.JSONArray; +import org.json.JSONObject; +import org.opengroup.osdu.core.common.logging.ILogger; import org.opengroup.osdu.core.common.logging.JaxRsDpsLog; -import org.opengroup.osdu.legal.jobs.LegalTagCompliance; +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.legal.LegalTag; +import org.opengroup.osdu.core.common.model.legal.ListLegalTagArgs; import org.opengroup.osdu.core.common.model.legal.StatusChangedTag; import org.opengroup.osdu.core.common.model.legal.StatusChangedTags; -import org.opengroup.osdu.core.common.model.legal.ListLegalTagArgs; +import org.opengroup.osdu.legal.jobs.LegalTagCompliance; +import org.opengroup.osdu.legal.logging.AuditLogger; +import org.opengroup.osdu.legal.FeatureFlagController; import org.opengroup.osdu.legal.provider.interfaces.ILegalTagPublisher; import org.opengroup.osdu.legal.provider.interfaces.ILegalTagRepository; import org.opengroup.osdu.legal.provider.interfaces.ILegalTagRepositoryFactory; import org.opengroup.osdu.legal.tags.dto.*; -import org.opengroup.osdu.core.common.model.legal.LegalTag; import org.opengroup.osdu.legal.tags.util.PersistenceExceptionToAppExceptionMapper; -import org.opengroup.osdu.core.common.model.http.AppException; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import jakarta.inject.Inject; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; +import static org.opengroup.osdu.legal.Constants.LEGAL_QUERY_API_UNION_OPERATOR; +import static org.opengroup.osdu.legal.Constants.LEGAL_QUERY_API_INTERSECTION_OPERATOR; +import static org.opengroup.osdu.legal.Constants.LEGAL_QUERY_API_QUERY_SEPARATOR; +import static org.opengroup.osdu.legal.Constants.LEGAL_QUERY_API_ATTRIBUTE_SEPARATOR; +import static org.opengroup.osdu.legal.Constants.LEGAL_QUERY_API_BETWEEN_END; +import static org.opengroup.osdu.legal.Constants.LEGAL_QUERY_API_BETWEEN_START; +import static org.opengroup.osdu.legal.Constants.LEGAL_QUERY_API_ADD_OPERATOR; +import static org.opengroup.osdu.legal.Constants.LEGAL_QUERY_API_FREE_TEXT_ATTRIBUTE; + +import java.util.logging.Logger; +import java.util.logging.StreamHandler; +import java.util.logging.ConsoleHandler; +import java.util.logging.SimpleFormatter; +import java.util.logging.Handler; +import java.util.logging.Level; @Service public class LegalTagService { - @Inject - public PersistenceExceptionToAppExceptionMapper exceptionMapper; //public for testing purposes only - - @Inject - private ILegalTagRepositoryFactory repositories; - @Inject - private LegalTagConstraintValidator validator; - @Inject - private AuditLogger auditLogger; - @Inject - private ILegalTagPublisher legalTagPublisher; - @Inject - private JaxRsDpsLog log; - - public LegalTagDto create(LegalTagDto legalTagDto, String tenantName) { - if (legalTagDto == null) - return null; - validator.isValidThrows(legalTagDto); - - ILegalTagRepository legalTagRepository = repositories.get(tenantName); - - LegalTag legalTag = LegalTagDto.convertFrom(legalTagDto); - String prefix = tenantName + "-"; - if (!legalTag.getName().startsWith(prefix)) { - legalTag.setName(prefix + legalTag.getName()); - } + @Inject + public PersistenceExceptionToAppExceptionMapper + exceptionMapper; // public for testing purposes only - validator.isValidThrows(legalTag); - legalTag.setDefaultId();//set id based on final name - legalTag.setIsValid(true); - exceptionMapper.run(legalTagRepository::create, legalTag, "Error creating LegalTag."); + @Inject private ILegalTagRepositoryFactory repositories; + @Inject private LegalTagConstraintValidator validator; + @Inject private AuditLogger auditLogger; + @Inject private ILegalTagPublisher legalTagPublisher; + @Inject private JaxRsDpsLog log; + @Inject private FeatureFlagController featureFlagController; - auditLogger.createdLegalTagSuccess(singletonList(legalTag.toString())); + public LegalTagDto create(LegalTagDto legalTagDto, String tenantName) { + if (legalTagDto == null) return null; + validator.isValidThrows(legalTagDto); - return LegalTagDto.convertTo(legalTag); + ILegalTagRepository legalTagRepository = repositories.get(tenantName); + + LegalTag legalTag = LegalTagDto.convertFrom(legalTagDto); + String prefix = tenantName + "-"; + if (!legalTag.getName().startsWith(prefix)) { + legalTag.setName(prefix + legalTag.getName()); } - public Boolean delete(String projectId, String name, DpsHeaders requestHeaders, String tenantName) { - if (Strings.isNullOrEmpty(name) || requestHeaders == null) - return false; + validator.isValidThrows(legalTag); + legalTag.setDefaultId(); // set id based on final name + legalTag.setIsValid(true); + exceptionMapper.run(legalTagRepository::create, legalTag, "Error creating LegalTag."); - LegalTag legalTag = getLegalTag(name, tenantName); - if (legalTag == null) - return true; - ILegalTagRepository legalTagRepository = repositories.get(tenantName); - Boolean result = exceptionMapper.run(legalTagRepository::delete, legalTag, "Error deleting LegalTag."); - if (result) { - publishMessageToPubSubOnDeletion(projectId, legalTag, requestHeaders); - auditLogger.deletedLegalTagSuccess(singletonList(legalTag.toString())); - } - return result; - } + auditLogger.createdLegalTagSuccess(singletonList(legalTag.toString())); - public LegalTagDto get(String name, String tenantName) { - if (Strings.isNullOrEmpty(name)) - return null; + return LegalTagDto.convertTo(legalTag); + } - LegalTagDtos tags = getBatch(new String[]{name}, tenantName); - if (tags == null || tags.getLegalTags() == null || tags.getLegalTags().isEmpty()) - return null; - else{ - auditLogger.readLegalTagSuccess(Collections.singletonList(name)); - return Iterables.get(tags.getLegalTags(), 0); - } + public Boolean delete( + String projectId, String name, DpsHeaders requestHeaders, String tenantName) { + if (Strings.isNullOrEmpty(name) || requestHeaders == null) return false; + + LegalTag legalTag = getLegalTag(name, tenantName); + if (legalTag == null) return true; + ILegalTagRepository legalTagRepository = repositories.get(tenantName); + Boolean result = + exceptionMapper.run(legalTagRepository::delete, legalTag, "Error deleting LegalTag."); + if (Boolean.TRUE.equals(result)) { + publishMessageToPubSubOnDeletion(projectId, legalTag, requestHeaders); + auditLogger.deletedLegalTagSuccess(singletonList(legalTag.toString())); } + return result; + } + + public LegalTagDto get(String name, String tenantName) { + if (Strings.isNullOrEmpty(name)) return null; - public Collection<LegalTag> listLegalTag(boolean valid, String tenantName) { - ILegalTagRepository legalTagRepository = repositories.get(tenantName); - ListLegalTagArgs args = new ListLegalTagArgs(); - args.setIsValid(valid); - return exceptionMapper.run(legalTagRepository::list, args, "Error retrieving LegalTag(s)."); + LegalTagDtos tags = getBatch(new String[] {name}, tenantName); + if (tags == null || tags.getLegalTags() == null || tags.getLegalTags().isEmpty()) return null; + else { + auditLogger.readLegalTagSuccess(Collections.singletonList(name)); + return Iterables.get(tags.getLegalTags(), 0); } + } + + public Collection<LegalTag> listLegalTag(boolean valid, String tenantName) { + ILegalTagRepository legalTagRepository = repositories.get(tenantName); + ListLegalTagArgs args = new ListLegalTagArgs(); + args.setIsValid(valid); + return exceptionMapper.run(legalTagRepository::list, args, "Error retrieving LegalTag(s)."); + } + + public LegalTagDtos list(boolean valid, String tenantName) { + Collection<LegalTag> tags = listLegalTag(valid, tenantName); + LegalTagDtos outputs = legalTagsToReadableLegalTags(tags); + List<String> names = + outputs.getLegalTags().stream().map(x -> x.getName()).collect(Collectors.toList()); + auditLogger.readLegalTagSuccess(names); + return outputs; + } + + public LegalTagDtos getBatch(String[] names, String tenantName) { + if (names == null) return null; + + Collection<LegalTag> legalTags = getLegalTags(names, tenantName); + auditLogger.readLegalTagSuccess(Collections.singletonList(String.join(", ", names))); + return legalTagsToReadableLegalTags(legalTags); + } + + public InvalidTagsWithReason validate(String[] names, String tenantName) { + List<InvalidTagWithReason> invalidTagsWithReason = new ArrayList<>(); + + if (names == null || names.length == 0) { + auditLogger.validateLegalTagSuccess(); + return new InvalidTagsWithReason(invalidTagsWithReason); + } + + List<String> notFoundNames = new ArrayList<>(asList(names)); - public LegalTagDtos list(boolean valid, String tenantName) { - Collection<LegalTag> tags = listLegalTag(valid, tenantName); - LegalTagDtos outputs = legalTagsToReadableLegalTags(tags); - List<String> names = outputs.getLegalTags().stream().map(x -> x.getName()).collect(Collectors.toList()); - auditLogger.readLegalTagSuccess(names); - return outputs; + Collection<LegalTag> legalTags = getLegalTags(names, tenantName); + if (legalTags == null || legalTags.size() == 0) { + for (String name : names) + generateInvalidTagsWithReason(invalidTagsWithReason, name, "LegalTag not found"); + return new InvalidTagsWithReason(invalidTagsWithReason); } - public LegalTagDtos getBatch(String[] names, String tenantName) { - if (names == null) - return null; + for (LegalTag tag : legalTags) { + notFoundNames.remove(tag.getName()); + String errors = validator.getErrors(tag); + if (errors != null) + generateInvalidTagsWithReason(invalidTagsWithReason, tag.getName(), errors); + } - Collection<LegalTag> legalTags = getLegalTags(names, tenantName); - auditLogger.readLegalTagSuccess(Collections.singletonList(String.join(", ", names))); - return legalTagsToReadableLegalTags(legalTags); + if (notFoundNames.size() > 0) { + for (String notFoundName : notFoundNames) + generateInvalidTagsWithReason(invalidTagsWithReason, notFoundName, "LegalTag not found"); } - public InvalidTagsWithReason validate(String[] names, String tenantName) { - List<InvalidTagWithReason> invalidTagsWithReason = new ArrayList<>(); + auditLogger.validateLegalTagSuccess(); - if (names == null || names.length == 0){ - auditLogger.validateLegalTagSuccess(); - return new InvalidTagsWithReason(invalidTagsWithReason); - } + return new InvalidTagsWithReason(invalidTagsWithReason); + } - List<String> notFoundNames = new ArrayList<>(asList(names)); + public LegalTagDto update(UpdateLegalTag newLegalTag, String tenantName) { + if (newLegalTag == null) return null; - Collection<LegalTag> legalTags = getLegalTags(names, tenantName); - if (legalTags == null || legalTags.size() == 0) { - for (String name : names) generateInvalidTagsWithReason(invalidTagsWithReason, name, "LegalTag not found"); - return new InvalidTagsWithReason(invalidTagsWithReason); - } + LegalTag currentLegalTag = getLegalTag(newLegalTag.getName(), tenantName); - for (LegalTag tag : legalTags) { - notFoundNames.remove(tag.getName()); - String errors = validator.getErrors(tag); - if (errors != null) - generateInvalidTagsWithReason(invalidTagsWithReason, tag.getName(), errors); - } + if (currentLegalTag == null) + throw AppException.legalTagDoesNotExistError(newLegalTag.getName()); - if (notFoundNames.size() > 0) { - for (String notFoundName : notFoundNames) - generateInvalidTagsWithReason(invalidTagsWithReason, notFoundName, "LegalTag not found"); - } + currentLegalTag.getProperties().setContractId(newLegalTag.getContractId()); + currentLegalTag.getProperties().setExpirationDate(newLegalTag.getExpirationDate()); + currentLegalTag.getProperties().setExtensionProperties(newLegalTag.getExtensionProperties()); + currentLegalTag.setDescription(newLegalTag.getDescription()); - auditLogger.validateLegalTagSuccess(); + validator.isValidThrows(currentLegalTag); - return new InvalidTagsWithReason(invalidTagsWithReason); - } + auditLogger.updatedLegalTagSuccess(Collections.singletonList(currentLegalTag.toString())); + + return update(currentLegalTag, tenantName); + } + + public LegalTagDto updateStatus(String legalTagName, Boolean isValid, String tenantName) { + if (legalTagName == null) return null; - public LegalTagDto update(UpdateLegalTag newLegalTag, String tenantName) { - if (newLegalTag == null) - return null; + LegalTag currentLegalTag = getLegalTag(legalTagName, tenantName); - LegalTag currentLegalTag = getLegalTag(newLegalTag.getName(), tenantName); + if (currentLegalTag == null) throw AppException.legalTagDoesNotExistError(legalTagName); - if (currentLegalTag == null) - throw AppException.legalTagDoesNotExistError(newLegalTag.getName()); + currentLegalTag.setIsValid(isValid); + return update(currentLegalTag, tenantName); + } - currentLegalTag.getProperties().setContractId(newLegalTag.getContractId()); - currentLegalTag.getProperties().setExpirationDate(newLegalTag.getExpirationDate()); - currentLegalTag.getProperties().setExtensionProperties(newLegalTag.getExtensionProperties()); - currentLegalTag.setDescription(newLegalTag.getDescription()); + private LegalTagDto update(LegalTag currentLegalTag, String tenantName) { + ILegalTagRepository legalTagRepository = repositories.get(tenantName); + LegalTag output = exceptionMapper.run(legalTagRepository::update, currentLegalTag, "error"); - validator.isValidThrows(currentLegalTag); + if (output == null) return null; + auditLogger.updatedLegalTagSuccess(singletonList(currentLegalTag.toString())); + return LegalTagDto.convertTo(output); + } - auditLogger.updatedLegalTagSuccess(Collections.singletonList(currentLegalTag.toString())); + private LegalTagDtos legalTagsToReadableLegalTags(Collection<LegalTag> legalTags) { + if (legalTags == null || legalTags.isEmpty()) return new LegalTagDtos(); - return update(currentLegalTag, tenantName); + List<LegalTagDto> convertedTags = new ArrayList<>(); + for (LegalTag tag : legalTags) { + if (tag == null) { + continue; + } + convertedTags.add(LegalTagDto.convertTo(tag)); + } + LegalTagDtos output = new LegalTagDtos(); + output.setLegalTags(convertedTags); + return output; + } + + private Collection<LegalTag> getLegalTags(String[] names, String tenantName) { + long[] ids = new long[names.length]; + String prefix = tenantName + "-"; + for (int i = 0; i < ids.length; i++) { + var legalTag = names[i]; + if (!legalTag.startsWith(prefix)) { + legalTag = prefix + legalTag; + } + ids[i] = LegalTag.getDefaultId(legalTag); + } + ILegalTagRepository legalTagRepository = repositories.get(tenantName); + return exceptionMapper.run(legalTagRepository::get, ids, "Error retrieving LegalTag(s)."); + } + + private LegalTag getLegalTag(String name, String tenantName) { + Collection<LegalTag> output = getLegalTags(new String[] {name}, tenantName); + return output == null || output.size() == 0 ? null : Iterables.get(output, 0); + } + + private void generateInvalidTagsWithReason( + List<InvalidTagWithReason> invalidTagsWithReason, String name, String reason) { + invalidTagsWithReason.add(new InvalidTagWithReason(name, reason)); + } + + private void publishMessageToPubSubOnDeletion( + String projectId, LegalTag legalTag, DpsHeaders headers) { + StatusChangedTags statusChangedTags = new StatusChangedTags(); + statusChangedTags + .getStatusChangedTags() + .add(new StatusChangedTag(legalTag.getName(), LegalTagCompliance.incompliant)); + try { + legalTagPublisher.publish(projectId, headers, statusChangedTags); + } catch (Exception e) { + log.error("Error when publishing legaltag status change to pubsub", e); + } + } + + public LegalTagDtos queryLegalTag(QueryLegalTag searchQuery, boolean valid, String tenantName) { + Collection<LegalTag> legalTags = listLegalTag(valid, tenantName); + + log.debug(String.format("DEBUG queryLegalTag Search query %s", searchQuery.toString())); + log.debug(String.format("DEBUG Size of legal tags retrieved: %d", legalTags.size())); + + LegalTagDtos outputs; + + Collection<LegalTag> matchedTags = readInputAndSearch(searchQuery, legalTags); + + log.debug(String.format("DEBUG Number of legaltags matched with input criteria %d", matchedTags.size())); + + outputs = legalTagsToReadableLegalTags(matchedTags); + List<String> names = + outputs.getLegalTags().stream().map(x -> x.getName()).toList(); + auditLogger.readLegalTagSuccess(names); + + return outputs; + } + + private Collection<LegalTag> readInputAndSearch(QueryLegalTag searchQuery, Collection<LegalTag> legalTags) { + + List<String> queryList = searchQuery.getQueryList(); + int limit = searchQuery.getLimit(); + List<String> operatorList = searchQuery.getOperatorList(); + boolean intersection = false; + boolean union = true; + String first; + + Collection<LegalTag> matchedTagList = null; + ArrayList<Collection<LegalTag>> matchTagArrayList = new ArrayList<>(); + + log.debug(String.format("DEBUG readInputAndSearch Search query %s %d", searchQuery.toString(), limit)); + + String extractedSearchQuery = null; + + if (null == operatorList) { + intersection = false; + union = true; + first = LEGAL_QUERY_API_UNION_OPERATOR; + log.debug("null operator"); + } else { + first = operatorList.iterator().next(); + if (StringUtils.containsAnyIgnoreCase(first, LEGAL_QUERY_API_INTERSECTION_OPERATOR)) { + intersection = true; + union = false; + } else if (StringUtils.containsAnyIgnoreCase(first, LEGAL_QUERY_API_UNION_OPERATOR)) { + intersection = false; + union = true; + } else if (StringUtils.containsAnyIgnoreCase(first, LEGAL_QUERY_API_ADD_OPERATOR)){ + intersection = false; + union = false; + } else { + log.info(String.format("invalid operator %s", first)); + throw new AppException(HttpStatus.SC_BAD_REQUEST, "Error parsing operator list", "Error parsing operator list, expected intersection or union"); + } } - public LegalTagDto updateStatus(String legalTagName, Boolean isValid, String tenantName) { - if (legalTagName == null) - return null; + ListIterator<String> queryListIt = queryList.listIterator(); - LegalTag currentLegalTag = getLegalTag(legalTagName, tenantName); + while (queryListIt.hasNext()) + { - if (currentLegalTag == null) - throw AppException.legalTagDoesNotExistError(legalTagName); + extractedSearchQuery = queryListIt.next(); + Matcher m = Pattern.compile("\\[([^]]*)").matcher(extractedSearchQuery); + while (m.find()) { - currentLegalTag.setIsValid(isValid); - return update(currentLegalTag, tenantName); + extractedSearchQuery = m.group(1); + } + matchedTagList = parseInputAndSearch(extractedSearchQuery, legalTags); + matchTagArrayList.add(matchedTagList); } - private LegalTagDto update(LegalTag currentLegalTag, String tenantName) { - ILegalTagRepository legalTagRepository = repositories.get(tenantName); - LegalTag output = exceptionMapper.run(legalTagRepository::update, currentLegalTag, "error"); + if (union) { + log.info(String.format("union %s", first)); + return matchTagArrayList.stream().flatMap(Collection::stream).collect(Collectors.toMap(LegalTag::getName, Function.identity(), (existing, replacement) -> existing)).values(); + } else if (intersection){ + + List<LegalTag> allTags = matchTagArrayList.stream().flatMap(Collection::stream).collect(Collectors.toList()); - if (output == null) - return null; - auditLogger.updatedLegalTagSuccess(singletonList(currentLegalTag.toString())); - return LegalTagDto.convertTo(output); + Collection<LegalTag> duplicateTags = allTags.stream() + .collect(Collectors.groupingBy(LegalTag::getName)) + .entrySet().stream() + .filter(entry -> entry.getValue().size() > 1) + .flatMap(entry -> entry.getValue().stream().limit(1)) + .collect(Collectors.toList()); + + return duplicateTags; + } else { // return add + return matchTagArrayList.stream().flatMap(Collection::stream).collect(Collectors.toList()); } - private LegalTagDtos legalTagsToReadableLegalTags(Collection<LegalTag> legalTags) { - if (legalTags == null || legalTags.isEmpty()) - return new LegalTagDtos(); + } + + private Collection<LegalTag> parseInputAndSearch( + String searchQuery, Collection<LegalTag> legalTags) { + + Collection<LegalTag> matchedTags = new ArrayList<>(); + + StringTokenizer st = null; + String attribute = null; + String pattern = null; + String searchedString = null; + String fromDate = null; + String toDate = null; + LocalDate fromlocalDate = null; + LocalDate toLocalDate = null; + boolean matchedTag = false; + LocalDate expirationDate = null; + + Iterator<LegalTag> legaliterator = legalTags.iterator(); + log.debug(String.format("parseInputAndSearch Search query %s", searchQuery)); + + if (searchQuery == null || searchQuery.equals("")) { + log.error("Null query"); + throw new AppException(HttpStatus.SC_BAD_REQUEST, "Null Search Query", "Error Null Search Query"); + } else if (searchQuery.contains(LEGAL_QUERY_API_ATTRIBUTE_SEPARATOR)) { + st = new StringTokenizer(searchQuery, LEGAL_QUERY_API_ATTRIBUTE_SEPARATOR); + log.debug(String.format("DEBUG readInputAndSearch contains =, %s", searchQuery)); + int tokens = st.countTokens(); + if (tokens == 2) { + attribute = st.nextToken().trim(); + pattern = st.nextToken().trim(); + } else { + log.error("invalid query input %s", searchQuery); + throw new AppException(HttpStatus.SC_BAD_REQUEST, "Error parsing attribute query", "Error parsing attribute query, expected attribute=string"); + } + } else if (searchQuery.contains(LEGAL_QUERY_API_BETWEEN_START)) { + // date + log.debug(String.format("DEBUG readInputAndSearch contains (), %s", searchQuery)); + searchedString = + searchQuery.substring(searchQuery.indexOf(LEGAL_QUERY_API_BETWEEN_START) + 1, searchQuery.lastIndexOf(LEGAL_QUERY_API_BETWEEN_END)); + + if (searchedString.contains(LEGAL_QUERY_API_QUERY_SEPARATOR)) { + st = new StringTokenizer(searchedString, LEGAL_QUERY_API_QUERY_SEPARATOR); + fromDate = st.nextToken().trim(); + toDate = st.nextToken().trim(); + } - List<LegalTagDto> convertedTags = new ArrayList<>(); - for (LegalTag tag : legalTags) { - if (tag == null) { - continue; - } - convertedTags.add(LegalTagDto.convertTo(tag)); + fromlocalDate = LocalDate.parse(fromDate); + toLocalDate = LocalDate.parse(toDate); + + } else { + log.debug(String.format("DEBUG readInputAndSearch contains free text search, %s", searchQuery)); + attribute = LEGAL_QUERY_API_FREE_TEXT_ATTRIBUTE; + pattern = searchQuery; + } + + log.debug(String.format("DEBUG parseInputAndSearch pattern: %s attribute %s, %s", pattern, attribute, searchQuery)); + while (null != legaliterator && legaliterator.hasNext()) { + LegalTag oneLegalTag = legaliterator.next(); + if (searchQuery.contains(LEGAL_QUERY_API_ATTRIBUTE_SEPARATOR)) { + matchedTag = searchInLegalTag(attribute, pattern, oneLegalTag); + } else if (searchQuery.contains(LEGAL_QUERY_API_BETWEEN_START)) { + expirationDate = oneLegalTag.getProperties().getExpirationDate().toLocalDate(); + matchedTag = + expirationDate.isAfter(fromlocalDate) && expirationDate.isBefore(toLocalDate); + } else if (attribute.contains(LEGAL_QUERY_API_FREE_TEXT_ATTRIBUTE)) { + matchedTag = searchInLegalTag(attribute, pattern, oneLegalTag); + } else { + log.error("Unexpected query state"); + throw new AppException(HttpStatus.SC_BAD_REQUEST, "Error processing query", "Error processing query"); } - LegalTagDtos output = new LegalTagDtos(); - output.setLegalTags(convertedTags); - return output; - } - - private Collection<LegalTag> getLegalTags(String[] names, String tenantName) { - long[] ids = new long[names.length]; - String prefix = tenantName + "-"; - for (int i = 0; i < ids.length; i++) { - var legalTag = names[i]; - if (!legalTag.startsWith(prefix)) { - legalTag = prefix + legalTag; - } - ids[i] = LegalTag.getDefaultId(legalTag); + + if (matchedTag) { + matchedTags.add(oneLegalTag); } - ILegalTagRepository legalTagRepository = repositories.get(tenantName); - return exceptionMapper.run(legalTagRepository::get, ids, "Error retrieving LegalTag(s)."); + } + + return matchedTags; + } + + public boolean checkAttributeForMatch(String attribute, + String pattern, + LegalTag legalTag + ) { + + org.opengroup.osdu.core.common.model.legal.Properties findInProperties = + legalTag.getProperties(); + BeanWrapper beanWrapper = new BeanWrapperImpl(findInProperties); + + log.debug("checkAttributeForMatch Attribute %s", attribute); + switch (attribute) { + case "countryOfOrigin" -> { + List<String> countryList = findInProperties.getCountryOfOrigin(); + return countryList.stream().anyMatch(pattern::equalsIgnoreCase); + } + case "expirationDate" -> { + Date expirationDate = findInProperties.getExpirationDate(); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); + String strDate = dateFormat.format(expirationDate); + if (StringUtils.contains(strDate, pattern)) { + log.debug(String.format("expirationDate Match found: %s, %s", strDate, pattern)); + return true; + } + return false; + } + case LEGAL_QUERY_API_FREE_TEXT_ATTRIBUTE -> { + log.debug(String.format("free text search: pattern : %s", pattern)); + return freeTextSearch(pattern, legalTag); + } + default -> { + log.debug(String.format("Attribute from properties: %s", attribute)); + Object value = beanWrapper.getPropertyValue(attribute); + log.debug(String.format("Attribute from properties: %s, Value: %s", attribute, value)); + return (value instanceof String && StringUtils.containsAnyIgnoreCase(value.toString().trim(), pattern)); + } + } + } + + public boolean freeTextSearch(String pattern, LegalTag legalTag) { + + + String name = legalTag.getName(); + if (StringUtils.containsAnyIgnoreCase(name.trim(), pattern)) { + return true; } - private LegalTag getLegalTag(String name, String tenantName) { - Collection<LegalTag> output = getLegalTags(new String[]{name}, tenantName); - return output == null || output.size() == 0 ? null : Iterables.get(output, 0); + String description = legalTag.getDescription(); + if (StringUtils.containsAnyIgnoreCase(description.trim(), pattern)){ + return true; } - - private void generateInvalidTagsWithReason(List<InvalidTagWithReason> invalidTagsWithReason, String name, String reason) { - invalidTagsWithReason.add(new InvalidTagWithReason(name, reason)); + + List<String> attributeList = new ArrayList<String>(); + attributeList.add("countryOfOrigin"); + attributeList.add("contractId"); + attributeList.add("originator"); + + if (Boolean.TRUE.equals(featureFlagController.isLegalTagQueryApiFreeTextAllFieldsFlagEnabled())) { + attributeList.add("expirationDate"); + attributeList.add("dataType"); + attributeList.add("securityClassification"); + attributeList.add("personalData"); + attributeList.add("exportClassification"); + } + + org.opengroup.osdu.core.common.model.legal.Properties findInProperties = + legalTag.getProperties(); + BeanWrapper beanWrapper = new BeanWrapperImpl(findInProperties); + Object value; + for (int i = 0; i < attributeList.size(); i++) { + value = beanWrapper.getPropertyValue(attributeList.get(i)); + if (StringUtils.containsAnyIgnoreCase(value.toString().trim(), pattern)) { + return true; + } } - private void publishMessageToPubSubOnDeletion(String projectId, LegalTag legalTag, DpsHeaders headers) { - StatusChangedTags statusChangedTags = new StatusChangedTags(); - statusChangedTags.getStatusChangedTags().add(new StatusChangedTag(legalTag.getName(), LegalTagCompliance.incompliant)); - try { - legalTagPublisher.publish(projectId, headers, statusChangedTags); - } catch (Exception e) { - log.error("Error when publishing legaltag status change to pubsub", e); + return false; + } + + public boolean searchInLegalTag(String attribute, String pattern, LegalTag legalTag) { + + BeanWrapper beanWrapper = new BeanWrapperImpl(legalTag); + + if (beanWrapper.isReadableProperty(attribute)) { + log.debug(String.format("DEBUG searchInLegaltag isReadableProperty %s %s", attribute, pattern)); + + final Object value = beanWrapper.getPropertyValue(attribute); + return (value instanceof String && StringUtils.containsAnyIgnoreCase(value.toString(), pattern)); + + } else { + org.opengroup.osdu.core.common.model.legal.Properties findInProperties = + legalTag.getProperties(); + beanWrapper = new BeanWrapperImpl(findInProperties); + if (beanWrapper.isReadableProperty(attribute)) { + + log.debug(String.format("DEBUG searchInLegaltag calling checkAttributeForMatch %s=%s", attribute, pattern)); + return checkAttributeForMatch(attribute, pattern, legalTag); + + } else if (attribute.contains(LEGAL_QUERY_API_FREE_TEXT_ATTRIBUTE)) { + log.debug(String.format("DEBUG searchInLegaltag calling checkAttributeForMatch %s=%s", attribute, pattern)); + boolean matchFound = checkAttributeForMatch(attribute, pattern, legalTag); + if (!matchFound) { + log.debug(String.format("DEBUG searchInLegaltag else extensionProperties %s %s", attribute, pattern)); + JSONObject jsonObject = new JSONObject(findInProperties.getExtensionProperties()); + return matchInExtensionPropertiesJSON(attribute, pattern, jsonObject); } + return true; + } else { + log.debug(String.format("DEBUG searchInLegaltag else extensionProperties %s %s", attribute, pattern)); + JSONObject jsonObject = new JSONObject(findInProperties.getExtensionProperties()); + return matchInExtensionPropertiesJSON(attribute, pattern, jsonObject); + } } -} + } + + private boolean matchInExtensionPropertiesJSON( + String attribute, String pattern, JSONObject jsonObject) { + + boolean didItMatch = false; + boolean didItMatchKey = false; + boolean didItMatchAny = false; + + String key = null; + Object value = null; + if (null != jsonObject && !didItMatch) { + Iterator<String> jsonKeyIter = jsonObject.keySet().iterator(); + while (null != jsonKeyIter && jsonKeyIter.hasNext()) { + key = jsonKeyIter.next(); + + if (jsonObject.get(key) instanceof JSONObject nestedJson) { + didItMatch = matchInExtensionPropertiesJSON(attribute, pattern, nestedJson); + + } else if (jsonObject.get(key) instanceof String) { + value = jsonObject.get(key); + didItMatchKey = key.equalsIgnoreCase(attribute) && StringUtils.containsAnyIgnoreCase(value.toString(), pattern); + if (!didItMatchKey) { + didItMatchAny = attribute.equalsIgnoreCase(LEGAL_QUERY_API_FREE_TEXT_ATTRIBUTE) && StringUtils.containsAnyIgnoreCase(value.toString(), pattern); + } + didItMatch = didItMatchKey || didItMatchAny; + + } else if (jsonObject.get(key) instanceof JSONArray jsonArray) { + for (int i = 0; i < jsonArray.length(); i++) { + if (jsonArray.get(i) instanceof JSONObject newJsonObj) { + didItMatch = matchInExtensionPropertiesJSON(attribute, pattern, newJsonObj); + if (didItMatch) break; + } + + if (jsonArray.get(i) instanceof String) { + value = jsonArray.get(i); + didItMatchKey = key.equalsIgnoreCase(attribute) && StringUtils.containsAnyIgnoreCase(value.toString(), pattern); + if (!didItMatchKey) { + log.debug(String.format("DEBUG Got %d Found %s=%s, Looking for %s=%s match: %b", i, key, value, attribute, pattern, didItMatch)); + didItMatchAny = attribute.equalsIgnoreCase(LEGAL_QUERY_API_FREE_TEXT_ATTRIBUTE) && StringUtils.containsAnyIgnoreCase(value.toString(), pattern); + } + didItMatch = didItMatchKey || didItMatchAny; + if (didItMatch) break; + } + } // end for + } else if (jsonObject.get(key) instanceof Boolean boolValue) { + boolean boolPattern = Boolean.parseBoolean(pattern); + didItMatch = key.equalsIgnoreCase(attribute) && (boolValue == boolPattern); + } + + if (didItMatch) return true; + } + } + return didItMatch; + } +} \ No newline at end of file diff --git a/legal-core/src/main/java/org/opengroup/osdu/legal/tags/dto/QueryLegalTag.java b/legal-core/src/main/java/org/opengroup/osdu/legal/tags/dto/QueryLegalTag.java new file mode 100644 index 0000000000000000000000000000000000000000..15636c5b5bb4ccaf5a178037a00fc557c8362154 --- /dev/null +++ b/legal-core/src/main/java/org/opengroup/osdu/legal/tags/dto/QueryLegalTag.java @@ -0,0 +1,27 @@ +package org.opengroup.osdu.legal.tags.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "Represents the Search Query objects for Legaltags.") +public class QueryLegalTag { + + @Schema(description = "Filter condition query") + private List<String> queryList; + + @Schema(description = "If there are multiple conditions need to be joined in by logical operators") + private List<String> operatorList; + + private String sortBy; + + private String sortOrder; + + private int limit; +} diff --git a/legal-core/src/main/resources/application.properties b/legal-core/src/main/resources/application.properties index ee30a05c477ff0c043441e225522556bf718b207..6089d48d6f9e5063e7ba13f0ca15906beca4b324 100644 --- a/legal-core/src/main/resources/application.properties +++ b/legal-core/src/main/resources/application.properties @@ -1,3 +1,5 @@ # Feature flag settings featureFlag.strategy=appProperty featureFlag.aboutToExpireLegalTag.enabled=false +featureFlag.legalTagQueryApi.enabled=true +featureFlag.legalTagQueryApiFreeTextAllFields.enabled=true diff --git a/legal-core/src/main/resources/swagger.properties b/legal-core/src/main/resources/swagger.properties index 07a0dcae3411c4d24da14131141a922d9b506c64..f9a1dbfebed6dc309b7e075898ebad24f02d4084 100644 --- a/legal-core/src/main/resources/swagger.properties +++ b/legal-core/src/main/resources/swagger.properties @@ -10,7 +10,7 @@ springdoc.api-docs.path=/api-docs #OpenAPI 3.0 - Legal properties swagger.apiTitle=Legal Service -swagger.apiDescription=Legal Service provides APIs to help with legal data governance in the Data Lake. +swagger.apiDescription=Legal Service provides APIs to help with legal data governance in the Data Lake. See [Legal Service Documentation](https://osdu.pages.opengroup.org/platform/security-and-compliance/legal). swagger.apiVersion=1.0.0 swagger.apiContactName=OSDU Data Platform Team swagger.apiContactEmail=dps@OSDU.org @@ -36,6 +36,8 @@ legalTagApi.validateLegalTags.summary=Retrieves the invalid LegalTag names with legalTagApi.validateLegalTags.description=This allows for the retrieval of the reason why your LegalTag is not valid. A maximum of 25 can be retrieved at once. legalTagApi.getLegalTagProperties.summary=Gets LegalTag property values. legalTagApi.getLegalTagProperties.description=This allows for the retrieval of allowed values for LegalTag properties. +legalTagApi.queryLegalTag.summary=Retrieves the legaltags which matches query criteria or none if there is no match. +legalTagApi.queryLegalTag.description=This allows query for specific attributes of legaltags including the attributes of extensionproperties. See [https://osdu.pages.opengroup.org/platform/security-and-compliance/legal/api/#legal-query](https://osdu.pages.opengroup.org/platform/security-and-compliance/legal/api/#legal-query) for more details. #LegalTag Status Job API related properties legalTagStatusJobApi.checkLegalTagStatusChanges.summary=Check LegalTag Compliance Job Status diff --git a/legal-core/src/test/java/org/opengroup/osdu/legal/controller/LegalTagControllerTests.java b/legal-core/src/test/java/org/opengroup/osdu/legal/controller/LegalTagControllerTests.java index 5d6edae9a86965ed4bba7e901adedcfb9b1c8d30..78c4918e47f4920025b5a7de715d20026c24ae07 100644 --- a/legal-core/src/test/java/org/opengroup/osdu/legal/controller/LegalTagControllerTests.java +++ b/legal-core/src/test/java/org/opengroup/osdu/legal/controller/LegalTagControllerTests.java @@ -12,16 +12,12 @@ import org.opengroup.osdu.core.common.model.http.DpsHeaders; import org.opengroup.osdu.core.common.model.http.RequestInfo; import org.opengroup.osdu.core.common.model.legal.AllowedLegaltagPropertyValues; import org.opengroup.osdu.core.common.model.tenant.TenantInfo; +import org.opengroup.osdu.legal.FeatureFlagController; import org.opengroup.osdu.legal.countries.LegalTagCountriesService; import org.opengroup.osdu.legal.logging.AuditLogger; import org.opengroup.osdu.legal.tags.LegalTagService; import org.opengroup.osdu.legal.tags.LegalTestUtils; -import org.opengroup.osdu.legal.tags.dto.InvalidTagsWithReason; -import org.opengroup.osdu.legal.tags.dto.LegalTagDto; -import org.opengroup.osdu.legal.tags.dto.LegalTagDtos; -import org.opengroup.osdu.legal.tags.dto.ReadablePropertyValues; -import org.opengroup.osdu.legal.tags.dto.RequestLegalTags; -import org.opengroup.osdu.legal.tags.dto.UpdateLegalTag; +import org.opengroup.osdu.legal.tags.dto.*; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -32,9 +28,13 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.eq; @@ -65,6 +65,9 @@ public class LegalTagControllerTests { @Mock private DpsHeaders dpsHeaders; + @Mock + private FeatureFlagController featureFlagControllerMock; + @InjectMocks private LegalTagController sut; @@ -293,4 +296,44 @@ public class LegalTagControllerTests { verify(auditLogger).readLegalPropertiesSuccess(any()); } -} + + @Test + public void should_return200AndLegalTags_when_matchFound() { + LegalTagDto tag = LegalTestUtils.createValidLegalTagDto("k"); + + List<LegalTagDto> tags = Arrays.asList(tag, tag, tag); + LegalTagDtos output = new LegalTagDtos(); + output.setLegalTags(tags); + + // isLegalQueryApi Feature Flag Enabled? + when(featureFlagControllerMock.isLegalTagQueryApiFlagEnabled()).thenReturn(true); + + + when(legalTagService.queryLegalTag(any(), anyBoolean(), any())).thenReturn(output); + + QueryLegalTag searchQuery = new QueryLegalTag(); + + searchQuery.setQueryList(Collections.singletonList("[AgreementPartyType= PurchaseOrganisation]")); + + ResponseEntity<LegalTagDtos> result = sut.queryLegalTag(searchQuery, true); + + assertEquals(HttpStatus.OK, result.getStatusCode()); + LegalTagDtos entity = (LegalTagDtos) result.getBody(); + + assertEquals(3, entity.getLegalTags().size(), 0); + } + + @Test + public void should_return405_legal_query_api_disabled() { + // isLegalQueryApi Feature Flag Enabled? + when(featureFlagControllerMock.isLegalTagQueryApiFlagEnabled()).thenReturn(false); + + QueryLegalTag searchQuery = new QueryLegalTag(); + searchQuery.setQueryList(Collections.singletonList("[AgreementPartyType= PurchaseOrganisation]")); + + ResponseEntity<LegalTagDtos> result = sut.queryLegalTag(searchQuery, true); + + assertEquals(HttpStatus.METHOD_NOT_ALLOWED, result.getStatusCode()); + } + +} \ No newline at end of file diff --git a/legal-core/src/test/java/org/opengroup/osdu/legal/tags/LegalTagServiceTests.java b/legal-core/src/test/java/org/opengroup/osdu/legal/tags/LegalTagServiceTests.java index 3673c211b943b9b158d0c0db6002563c00b52e0e..57b0219ba02c7453799171caa70b190cf0965e6b 100644 --- a/legal-core/src/test/java/org/opengroup/osdu/legal/tags/LegalTagServiceTests.java +++ b/legal-core/src/test/java/org/opengroup/osdu/legal/tags/LegalTagServiceTests.java @@ -1,5 +1,6 @@ package org.opengroup.osdu.legal.tags; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -11,27 +12,26 @@ 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.legal.LegalTag; +import org.opengroup.osdu.core.common.model.legal.Properties; +import org.opengroup.osdu.core.common.model.tenant.TenantInfo; +import org.opengroup.osdu.legal.FeatureFlagController; import org.opengroup.osdu.legal.logging.AuditLogger; import org.opengroup.osdu.legal.provider.interfaces.ILegalTagPublisher; import org.opengroup.osdu.legal.provider.interfaces.ILegalTagRepository; import org.opengroup.osdu.legal.provider.interfaces.ILegalTagRepositoryFactory; -import org.opengroup.osdu.legal.tags.dto.InvalidTagWithReason; -import org.opengroup.osdu.legal.tags.dto.InvalidTagsWithReason; -import org.opengroup.osdu.legal.tags.dto.LegalTagDto; -import org.opengroup.osdu.legal.tags.dto.LegalTagDtos; -import org.opengroup.osdu.legal.tags.dto.UpdateLegalTag; +import org.opengroup.osdu.legal.tags.dto.*; import org.opengroup.osdu.legal.tags.util.PersistenceExceptionToAppExceptionMapper; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.sql.Date; +import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -53,10 +53,13 @@ public class LegalTagServiceTests { private ILegalTagPublisher messagePublisherMock; @Mock private JaxRsDpsLog log; + @Mock + private FeatureFlagController featureFlagControllerMock; @InjectMocks private LegalTagService sut; + @Before public void setup() { sut.exceptionMapper = mapper; @@ -91,7 +94,7 @@ public class LegalTagServiceTests { LegalTagDto legalTagDto = LegalTestUtils.createValidLegalTagDto("account-1"); LegalTagService testService = createSut(output); - testService.create(legalTagDto,"tenant2"); + testService.create(legalTagDto, "tenant2"); verify(auditLogger).createdLegalTagSuccess(any()); } @@ -101,7 +104,7 @@ public class LegalTagServiceTests { sut = createSut(); LegalTagDto legalTagDto = LegalTestUtils.createValidLegalTagDto("1"); - legalTagDto = sut.create(legalTagDto,"tenant"); + legalTagDto = sut.create(legalTagDto, "tenant"); LegalTag legalTag = LegalTagDto.convertFrom(legalTagDto); assertEquals(-1306694706L, (long) legalTag.getId()); } @@ -112,7 +115,7 @@ public class LegalTagServiceTests { sut = createSut(legalTag); LegalTagDto legalTagDto = LegalTagDto.convertTo(legalTag); - legalTagDto = sut.create(legalTagDto,"tenant1"); + legalTagDto = sut.create(legalTagDto, "tenant1"); assertEquals("tenant1-mylegaltag", legalTagDto.getName()); assertEquals((Long) LegalTag.getDefaultId("tenant1-mylegaltag"), LegalTagDto.convertFrom(legalTagDto).getId()); @@ -135,7 +138,7 @@ public class LegalTagServiceTests { LegalTagDto legalTagDto = LegalTestUtils.createValidLegalTagDto("mylegaltag"); legalTagDto.setName("Tenant1-" + legalTagDto.getName()); - legalTagDto = sut.create(legalTagDto,"tenant1"); + legalTagDto = sut.create(legalTagDto, "tenant1"); assertEquals("tenant1-Tenant1-mylegaltag", legalTagDto.getName()); //verify isvalidthrows was called @@ -149,7 +152,7 @@ public class LegalTagServiceTests { sut = createSut(legalTag); LegalTagDto dto = LegalTagDto.convertTo(legalTag); - dto = sut.create(dto,"tenant2"); + dto = sut.create(dto, "tenant2"); assertEquals("tenant2-mylegaltag", dto.getName()); assertEquals((Long) LegalTag.getDefaultId("tenant2-mylegaltag"), LegalTagDto.convertFrom(dto).getId()); @@ -177,7 +180,7 @@ public class LegalTagServiceTests { public void should_ReturnNull_When_GivenNameThatDoesNotExist() { sut = createSut(null); - LegalTagDto result = sut.get("mykind","tenant1"); + LegalTagDto result = sut.get("mykind", "tenant1"); assertEquals(null, result); } @@ -186,7 +189,7 @@ public class LegalTagServiceTests { public void should_ReturnNull_When_ErrorOccurredLegalTagsListIsNull() { sut = createSut(null); when(legalTagRepositoryMock.get(any())).thenReturn(null); - LegalTagDto result = sut.get("mykind","tenant1"); + LegalTagDto result = sut.get("mykind", "tenant1"); assertEquals(null, result); } @@ -251,7 +254,7 @@ public class LegalTagServiceTests { public void should_returnNull_when_updateStatusIsGivenNullLegalTagOrHeaders() { sut = createSut(); - LegalTagDto result = sut.updateStatus(null, true,"tenant1"); + LegalTagDto result = sut.updateStatus(null, true, "tenant1"); assertNull(result); result = sut.updateStatus("hi", true, null); @@ -264,7 +267,7 @@ public class LegalTagServiceTests { when(legalTagRepositoryMock.get(any())).thenReturn(null); try { - sut.updateStatus("legaltag2023", true,"tenant1"); + sut.updateStatus("legaltag2023", true, "tenant1"); fail("Expected error"); } catch (AppException ex) { assertEquals(404, ex.getError().getCode()); @@ -361,7 +364,7 @@ public class LegalTagServiceTests { sut = createSut(); String[] input = new String[]{"abc", "123"}; - LegalTagDtos result = sut.getBatch(input,"tenant2"); + LegalTagDtos result = sut.getBatch(input, "tenant2"); assertEquals(2, result.getLegalTags().size()); verify(auditLogger).readLegalTagSuccess(any()); } @@ -397,10 +400,11 @@ public class LegalTagServiceTests { InvalidTagsWithReason result = sut.validate(null, "tenant1"); assertEquals(0, result.getInvalidLegalTags().size()); } + @Test public void should_returnNull_when_givenEmptyNames() { sut = createSut(); - InvalidTagsWithReason result = sut.validate(new String[] {}, "tenant1"); + InvalidTagsWithReason result = sut.validate(new String[]{}, "tenant1"); assertEquals(0, result.getInvalidLegalTags().size()); } @@ -510,6 +514,208 @@ public class LegalTagServiceTests { assertEquals("kind2", result.getName()); } + @Test + public void should_returnMatchedLegalTags() { + sut = createSutWithExtensionProperties_test1(); + + Collection<LegalTag> legalTagDtos = sut.listLegalTag(true, "tenant1"); + + assertFalse("collection size > 0", legalTagDtos.isEmpty()); + + } + + @Test + public void test_attribute_match() { + // attributes expiration, countryOfOrigin, any + LegalTag output = new LegalTag(); + output.setName("opendes-osdu-testing-1"); + output.setDescription("legal tag description test"); + Properties properties = new Properties(); + properties.setCountryOfOrigin(Collections.singletonList("US")); + properties.setContractId("123450"); + properties.setContractId("123450"); + properties.setExpirationDate(Date.valueOf("2023-11-20")); + output.setProperties(properties); + output.setIsValid(true); + sut = createSut(output); + Boolean found = sut.checkAttributeForMatch("expirationDate", "2023-11-20", output); + assert(found.equals(true)); + Boolean found2 = sut.checkAttributeForMatch("any", "test", output); + assert(found2.equals(true)); + Boolean found3 = sut.checkAttributeForMatch("countryOfOrigin", "US", output); + assert(found3.equals(true)); + Boolean found4 = sut.checkAttributeForMatch("dataType", "US", output); + assert(found4.equals(false)); + + HashMap<String, Object> extensionPropertiesMap = new HashMap<>(); + extensionPropertiesMap.put("TestBoolean", false); + properties.setExtensionProperties(extensionPropertiesMap); + } + + public void test_properties(String testQuery, Boolean expectFound) { + sut = createSutWithExtensionProperties_test1(); + + QueryLegalTag searchLegalTag = new QueryLegalTag(); + searchLegalTag.setQueryList(Collections.singletonList(testQuery)); + + LegalTagDtos resultDto = sut.queryLegalTag(searchLegalTag, true, "tenant1"); + if (expectFound) { + assertFalse(String.format("expected collection isNotEmpty, Query: %s", testQuery), resultDto.getLegalTags().isEmpty()); + } else { + assertTrue(String.format("expected collection isEmpty, Query: %s", testQuery), resultDto.getLegalTags().isEmpty()); + } + + } + + public void test_properties(String testQuery, Boolean expectFound, String operator) { + sut = createSutWithExtensionProperties_test1(); + + QueryLegalTag searchLegalTag = new QueryLegalTag(); + searchLegalTag.setQueryList(Collections.singletonList(testQuery)); + searchLegalTag.setOperatorList(Collections.singletonList(operator)); + + LegalTagDtos resultDto = sut.queryLegalTag(searchLegalTag, true, "tenant1"); + if (expectFound) { + assertFalse(String.format("expected collection isNotEmpty, Query: %s", testQuery), resultDto.getLegalTags().isEmpty()); + } else { + assertTrue(String.format("expected collection isEmpty, Query: %s", testQuery), resultDto.getLegalTags().isEmpty()); + } + + } + + public void test_properties_exceptions(String testQuery, String message) { + + Exception e = assertThrows(AppException.class, ()->{ + test_properties(testQuery, true); + }); + String m = e.getMessage(); + assertEquals(m, message); + } + + public void test_properties_exceptions(String testQuery, String message, String operator) { + + Exception e = assertThrows(AppException.class, ()->{ + test_properties(testQuery, true, operator); + }); + String m = e.getMessage(); + assertEquals(m, message); + } + + @Test + public void test_legaltag_exceptions_whenExceptionThrown_thenAssertionSucceeds() { + // test exceptions + test_properties_exceptions("[foo=]", "Error parsing attribute query, expected attribute=string"); + test_properties_exceptions("[any=]", "Error parsing attribute query, expected attribute=string"); + test_properties_exceptions("[]", "Error Null Search Query"); + test_properties_exceptions("", "Error Null Search Query"); + test_properties_exceptions("", "Error parsing operator list, expected intersection or union", "foo"); + test_properties_exceptions("[name=test]", "Error parsing operator list, expected intersection or union", "bar"); + } + + @Test + public void test_individual_legaltag_properties() + { + // tag + test_properties("[name=opendes-osdu-testing-1]", true); + test_properties("[description=legal tag description test]", true); + // properties + test_properties("[countryOfOrigin=US]", true); + test_properties("[contractId=123450]", true); + test_properties("[expirationDate=2023-11]", true); + test_properties("[expirationDate between (2023-03-04,2023-11-22)]", true); + test_properties("[originator=Schul]", true); + test_properties("[originator=schul]", true); + test_properties("[dataType=third party]", true); + test_properties("[securityClassification=private]", true); + test_properties("[personalData=no personal]", true); + test_properties("[exportClassification=ear99]", true); + // extension properties + test_properties("[AgreementIdentifier=test]", true); + test_properties("[EffectiveDate=2023-01]", true); + test_properties("[TerminationDate=2099-12]", true); + test_properties("[CountryCode=GBL]", true); + test_properties("[TestingMap=ABC]", true); + test_properties("[AffiliateEnablementIndicator=true]", true); + test_properties("[expirationDate between (2023-03-04,2023-11-22)]", true); + test_properties("[TestBoolean1=false]", true); + test_properties("[TestBoolean2=true]", true); + test_properties("[TestBoolean5=true]", true); + test_properties("[TestBoolean4=true]", true); + test_properties("[TestBoolean3=false]", true); + test_properties("[TestBoolean9=false]", false); + // any + when(featureFlagControllerMock.isLegalTagQueryApiFreeTextAllFieldsFlagEnabled()).thenReturn(true); + test_properties("[test]", true); + test_properties("[any=test]", true); + test_properties("[notfoundhere]", false); + // test nested extended properties + test_properties("[AgreementParty=some test data]", true); + } + + @Test + public void test_operators() + { + test_properties("[name=opendes-osdu-testing-1]", true, "union"); + test_properties("[name=opendes-osdu-testing-1]", true, "intersection"); + test_properties("[name=opendes-osdu-testing-1]", true, "add"); + } + + private LegalTagService createSutWithExtensionProperties_test1() { + LegalTag output = new LegalTag(); + output.setName("opendes-osdu-testing-1"); + output.setDescription("legal tag description test"); + output.setIsValid(true); + Properties properties = new Properties(); + properties.setCountryOfOrigin(Collections.singletonList("US")); + properties.setContractId("123450"); + properties.setExpirationDate(Date.valueOf("2023-11-20")); + properties.setOriginator("Schulmberger"); + properties.setDataType("Third Party Data"); + properties.setSecurityClassification("Private"); + properties.setPersonalData("No Personal Data"); + properties.setExportClassification("EAR99"); + HashMap<String, Object> extensionPropertiesMap = new HashMap<>(); + extensionPropertiesMap.put("AgreementIdentifier", "dz-test-0"); + extensionPropertiesMap.put("EffectiveDate", "2023-01-00T00:00:00"); + extensionPropertiesMap.put("TerminationDate", "2099-12-31T00:00:00"); + extensionPropertiesMap.put("AffiliateEnablementIndicator", "true"); + extensionPropertiesMap.put("TestBoolean1", "this is false"); + extensionPropertiesMap.put("TestBoolean2", "this is true"); + extensionPropertiesMap.put("TestBoolean3", false); + extensionPropertiesMap.put("TestBoolean4", true); + extensionPropertiesMap.put("TestBoolean5", true); + extensionPropertiesMap.put("TestBooleanString1", "true"); + extensionPropertiesMap.put("TestBooleanString2", "false"); + extensionPropertiesMap.put("TestBooleanArray1", "[true]"); + extensionPropertiesMap.put("TestBooleanArray2", "[false]"); + HashMap<String, Object> agreementPartyMap = new HashMap<String, Object>(); + agreementPartyMap.put("AgreementPartyType", "omnibus"); + agreementPartyMap.put("AgreementParty", "random things"); + HashMap<String, Object> dataCoverageMap = new HashMap<String, Object>(); + dataCoverageMap.put("Country", "osdu:master-data--GeopoliticalEntity:Global:"); + dataCoverageMap.put("CountryCode", "GBL"); + extensionPropertiesMap.put("DataCoverage", dataCoverageMap); + + ArrayList<HashMap> agreementList = new ArrayList<HashMap>(); + agreementList.add(agreementPartyMap); + agreementPartyMap.put("AgreementPartyType", "test AgreementPartyType"); + agreementPartyMap.put("AgreementParty", "some test data"); + agreementList.add(agreementPartyMap); + + ArrayList<String> someArbitList = new ArrayList<String>(); + someArbitList.add("ABC"); + someArbitList.add("123"); + + extensionPropertiesMap.put("TestingMap", someArbitList); + + extensionPropertiesMap.put("AgreementParties", agreementList); + + + properties.setExtensionProperties(extensionPropertiesMap); + output.setProperties(properties); + return createSut(output); + } + private LegalTagService createSut() { LegalTag output = new LegalTag(); output.setName("kind2"); diff --git a/provider/legal-aws/src/main/resources/application.properties b/provider/legal-aws/src/main/resources/application.properties index bfce0a153dfa3723b7bc5bb6bb65d680edc8e651..bf187c8aef710e608dc2c98a5c8ba6c4f4c968f7 100644 --- a/provider/legal-aws/src/main/resources/application.properties +++ b/provider/legal-aws/src/main/resources/application.properties @@ -15,7 +15,8 @@ LOG_PREFIX=legal server.servlet.contextPath=/api/legal/v1/ -logging.level.org.springframework.web=${LOG_LEVEL} +logging.level.org.springframework.web=${LOG_LEVEL:INFO} +logging.level.org.opengroup.osdu=${LOG_LEVEL:INFO} server.port=${APPLICATION_PORT} ## AWS Lambda configuration @@ -63,9 +64,11 @@ repository.implementation=${LEGAL_SERVICE_REPOSITORY_IMPLEMENTATION:dynamodb} #Tomcat limits server.tomcat.threads.max=${TOMCAT_THREADS_MAX:300} -# About to expire LegalTag notifications -legaltag.expirationAlerts=1m,2w,1d - # Feature flag settings featureFlag.strategy=appProperty featureFlag.aboutToExpireLegalTag.enabled=true +featureFlag.legalTagQueryApi.enabled=true +featureFlag.legalTagQueryApiFreeTextAllFields.enabled=true + +# About to expire LegalTag notifications +legaltag.expirationAlerts=1m,2w,1d \ No newline at end of file diff --git a/provider/legal-azure/src/main/resources/application.properties b/provider/legal-azure/src/main/resources/application.properties index 2cca9de6b96a2fc53836daec9415e3b3d88c81b0..efa5988af013c160ad87642457f76caa494b124d 100644 --- a/provider/legal-azure/src/main/resources/application.properties +++ b/provider/legal-azure/src/main/resources/application.properties @@ -83,3 +83,5 @@ api.server.fullUrl.enabled=${swaggerFullUrlEnabled:true} # Feature flag settings featureFlag.strategy=appProperty featureFlag.aboutToExpireLegalTag.enabled=false +featureFlag.legalTagQueryApi.enabled=true +featureFlag.legalTagQueryApiFreeTextAllFields.enabled=false \ No newline at end of file diff --git a/provider/legal-gc/src/main/resources/application.properties b/provider/legal-gc/src/main/resources/application.properties index 5b6d57bb812aa11c32da4ae25481e323c5013cab..79a5beaa4da8d1f9830f8a20089f6d4324ce69de 100644 --- a/provider/legal-gc/src/main/resources/application.properties +++ b/provider/legal-gc/src/main/resources/application.properties @@ -27,3 +27,5 @@ propertyResolver.strategy=partition # Feature flag settings featureFlag.strategy=appProperty featureFlag.aboutToExpireLegalTag.enabled=false +featureFlag.legalTagQueryApi.enabled=false +featureFlag.legalTagQueryApiFreeTextAllFields.enabled=false \ No newline at end of file diff --git a/publish.yml b/publish.yml new file mode 100644 index 0000000000000000000000000000000000000000..43510ace19cf49ee8b72154e60b8011b9a3f2a15 --- /dev/null +++ b/publish.yml @@ -0,0 +1,30 @@ +run-test-pages: + stage: build + image: python:latest + script: + - cd docs + - pip install mkdocs-material + - pip install mkdocs-git-revision-date-plugin + - mkdocs build --site-dir ../test + allow_failure: true + artifacts: + paths: + - test + when: manual + +pages: + stage: publish + image: python:latest + tags: ["osdu-medium"] + script: + - cd docs + - pip install --default-timeout=1000 mkdocs-material + - pip install mkdocs-git-revision-date-plugin + - mkdocs build --site-dir ../public + allow_failure: true + needs: [] + artifacts: + paths: + - public +# rules: +# - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' \ No newline at end of file diff --git a/testing/legal-test-aws/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryLegalTagsApiAcceptance.java b/testing/legal-test-aws/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryLegalTagsApiAcceptance.java new file mode 100644 index 0000000000000000000000000000000000000000..db2f3ccab374b461a4c49b15bea7fbcd71cb6242 --- /dev/null +++ b/testing/legal-test-aws/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryLegalTagsApiAcceptance.java @@ -0,0 +1,22 @@ +package org.opengroup.osdu.legal.acceptanceTests; + +import org.junit.After; +import org.junit.Before; +import org.opengroup.osdu.legal.util.AwsLegalTagUtils; + +public class TestQueryLegalTagsApiAcceptance extends QueryLegalTagsApiAcceptanceTests { + + @Before + @Override + public void setup() throws Exception { + this.legalTagUtils = new AwsLegalTagUtils(); + super.setup(); + } + + @After + @Override + public void teardown() throws Exception { + super.teardown(); + this.legalTagUtils = null; + } +} diff --git a/testing/legal-test-azure/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java b/testing/legal-test-azure/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java new file mode 100644 index 0000000000000000000000000000000000000000..c79835ddcb9065aaac14b003e49142b8b0ef0c2c --- /dev/null +++ b/testing/legal-test-azure/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java @@ -0,0 +1,35 @@ +package org.opengroup.osdu.legal.acceptanceTests; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.opengroup.osdu.legal.util.AzureLegalTagUtils; + +public class TestQueryApiAcceptance extends QueryLegalTagsApiAcceptanceTests { + + @Before + @Override + public void setup() throws Exception { + this.legalTagUtils = new AzureLegalTagUtils(); + super.setup(); + } + + @After + @Override + public void teardown() throws Exception { + super.teardown(); + this.legalTagUtils = null; + } + + @Test + @Override + public void should_return307_when_makingHttpRequest() throws Exception { + // services are enforced to run in https on Azure + } + + @Test + @Override + public void should_return401_when_makingHttpRequestWithoutToken() throws Exception { + // services are enforced to run in https on Azure + } +} \ No newline at end of file diff --git a/testing/legal-test-baremetal/src/test/java/legal/acceptanceTests/TestQueryApiAcceptance.java b/testing/legal-test-baremetal/src/test/java/legal/acceptanceTests/TestQueryApiAcceptance.java new file mode 100644 index 0000000000000000000000000000000000000000..0f9bc455ad768be4d5723f8f13814d595060b0b9 --- /dev/null +++ b/testing/legal-test-baremetal/src/test/java/legal/acceptanceTests/TestQueryApiAcceptance.java @@ -0,0 +1,23 @@ +package legal.acceptanceTests; + +import legal.util.AnthosLegalTagUtils; +import org.junit.After; +import org.junit.Before; +import org.opengroup.osdu.legal.acceptanceTests.QueryLegalTagsApiAcceptanceTests; + +public class TestQueryApiAcceptance extends QueryLegalTagsApiAcceptanceTests { + + @Before + @Override + public void setup() throws Exception { + this.legalTagUtils = new AnthosLegalTagUtils(); + super.setup(); + } + + @After + @Override + public void teardown() throws Exception { + super.teardown(); + this.legalTagUtils = null; + } +} diff --git a/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/acceptanceTests/QueryLegalTagsApiAcceptanceTests.java b/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/acceptanceTests/QueryLegalTagsApiAcceptanceTests.java new file mode 100644 index 0000000000000000000000000000000000000000..847c0b1e217b29e41ac0eed9d23cf23db2ffd306 --- /dev/null +++ b/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/acceptanceTests/QueryLegalTagsApiAcceptanceTests.java @@ -0,0 +1,203 @@ +package org.opengroup.osdu.legal.acceptanceTests; + +import org.opengroup.osdu.legal.util.AcceptanceBaseTest; +import org.opengroup.osdu.legal.util.LegalTagUtils; +import org.opengroup.osdu.legal.util.TestUtils; +import com.sun.jersey.api.client.ClientResponse; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import java.util.List; +import java.util.Arrays; + +public abstract class QueryLegalTagsApiAcceptanceTests extends AcceptanceBaseTest { + + protected String name; + protected static final List<Integer> OK_VALUES = Arrays.asList(200, 405); + protected static final List<Integer> BAD_REQUEST_VALUES = Arrays.asList(400, 405); + + @Test + public void should_return400Error_when_givingInvalidPayload1() throws Exception { + ClientResponse response = send("", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(BAD_REQUEST_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200E_when_giving_empty_queryList() throws Exception { + ClientResponse response = send("{\"queryList\":[]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return400Error_when_givingInvalidPayload_no_query_string() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(BAD_REQUEST_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_when_giving_notfound_queryList1() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name==notfound\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_when_giving_notfound_queryList2() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=notfound\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_queryList_countryOfOrigin() throws Exception { + ClientResponse response = send("{\"queryList\":[\"countryOfOrigin=US\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_queryList_dataType() throws Exception { + ClientResponse response = send("{\"queryList\":[\"dataType=public\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_queryList_personalData1() throws Exception { + ClientResponse response = send("{\"queryList\":[\"personalData=No\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_queryList_personalData2() throws Exception { + ClientResponse response = send("{\"queryList\":[\"personalData=No Personal\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_queryList_exportClassification() throws Exception { + ClientResponse response = send("{\"queryList\":[\"exportClassification=EAR99\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_queryList_expirationDate() throws Exception { + ClientResponse response = send("{\"queryList\":[\"expirationDate between (2023-01-01, 2099-12-31)\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return400Error_when_givingInvalidPayload_no_attribute() throws Exception { + ClientResponse response = send("{\"queryList\":[\"=test\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(BAD_REQUEST_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_union_strange() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=*?\"],\"operatorList\":[\"union\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_without_operator() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_union() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\"],\"operatorList\":[\"union\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_intersection() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\"],\"operatorList\":[\"intersection\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_add() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\"],\"operatorList\":[\"add\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_add_with_two() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\", \"description=test\"],\"operatorList\":[\"add\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_union_with_two1() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\", \"description=test\"],\"operatorList\":[\"union\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_intersection_with_two() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\", \"description=test\"],\"operatorList\":[\"intersection\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_intersection_with_three() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\", \"description=test\", \"exportClassification=EAR99\"],\"operatorList\":[\"intersection\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_union_with_three() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\", \"description=test\", \"exportClassification=EAR99\"],\"operatorList\":[\"union\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_free_text1() throws Exception { + ClientResponse response = send("{\"queryList\":[\"any=test\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_free_text2() throws Exception { + ClientResponse response = send("{\"queryList\":[\"test\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_with_match_name_operator_union_with_two2() throws Exception { + ClientResponse response = send("{\"queryList\":[\"name=test\", \"description=test\"],\"operatorList\":[\"union\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_attribute_does_not_exist() throws Exception { + ClientResponse response = send("{\"queryList\":[\"doesnotexist=test\"],\"operatorList\":[\"union\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + public void should_return200_extension_properties() throws Exception { + ClientResponse response = send("{\"queryList\":[\"AgreementPartyType=enabled\"],\"operatorList\":[\"union\"]}", "?valid=true", TestUtils.getMyDataPartition()); + assertTrue(OK_VALUES.contains(response.getStatus())); + } + + @Test + @Override + public void should_return401_when_makingHttpRequestWithoutToken()throws Exception{ + name = LegalTagUtils.createRandomNameTenant(); + super.should_return401_when_makingHttpRequestWithoutToken(); + } + + @Override + protected String getBody() { + return LegalTagUtils.createRetrieveBatchBody(name); + } + + @Override + protected String getApi() { + return "legaltags:query"; + } + + @Override + protected String getHttpMethod() { + return "POST"; + } +} \ No newline at end of file diff --git a/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/AcceptanceBaseTest.java b/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/AcceptanceBaseTest.java index 9ac6077255af75cc6c5724400ed901df8e5d0c17..35a3b111ac4ab67acc4f1652f88f39ad567219fa 100644 --- a/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/AcceptanceBaseTest.java +++ b/testing/legal-test-core/src/main/java/org/opengroup/osdu/legal/util/AcceptanceBaseTest.java @@ -85,6 +85,13 @@ public abstract class AcceptanceBaseTest { return response; } + protected ClientResponse send(String requestBody, String query, String tenant) throws Exception { + Map<String, String> headers = legalTagUtils.getHeaders(); + headers.put(DATA_PARTITION_ID, tenant); + return legalTagUtils.send(this.getApi(), this.getHttpMethod(), legalTagUtils.accessToken(), requestBody, + query, headers); + } + protected ClientResponse validateAccess(int expectedResponse) throws Exception { Map<String, String> headers = new HashMap<>(); headers.put(DATA_PARTITION_ID, LegalTagUtils.getMyDataPartition()); diff --git a/testing/legal-test-gc/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java b/testing/legal-test-gc/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java new file mode 100644 index 0000000000000000000000000000000000000000..e7728504b4282cc69d36d4aeda7919bf6b4c5f0c --- /dev/null +++ b/testing/legal-test-gc/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java @@ -0,0 +1,22 @@ +package org.opengroup.osdu.legal.acceptanceTests; + +import org.junit.After; +import org.junit.Before; +import org.opengroup.osdu.legal.util.GCPLegalTagUtils; + +public class TestQueryApiAcceptance extends QueryLegalTagsApiAcceptanceTests { + + @Before + @Override + public void setup() throws Exception { + this.legalTagUtils = new GCPLegalTagUtils(); + super.setup(); + } + + @After + @Override + public void teardown() throws Exception { + super.teardown(); + this.legalTagUtils = null; + } +} diff --git a/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java new file mode 100644 index 0000000000000000000000000000000000000000..67aa7d24770adbff614744fe6ecbadc54318c8ca --- /dev/null +++ b/testing/legal-test-ibm/src/test/java/org/opengroup/osdu/legal/acceptanceTests/TestQueryApiAcceptance.java @@ -0,0 +1,35 @@ +package org.opengroup.osdu.legal.acceptanceTests; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.opengroup.osdu.legal.util.IBMLegalTagUtils; + +public class TestQueryApiAcceptance extends QueryLegalTagsApiAcceptanceTests { + + @Before + @Override + public void setup() throws Exception { + this.legalTagUtils = new IBMLegalTagUtils(); + super.setup(); + } + + @After + @Override + public void teardown() throws Exception { + super.teardown(); + this.legalTagUtils = null; + } + + @Test + @Override + public void should_return307_when_makingHttpRequest() { + // services are enforced to run in https on OpenShift + } + + @Test + @Override + public void should_return401_when_makingHttpRequestWithoutToken() { + // services are enforced to run in https on OpenShift + } +}