diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2c055b --- /dev/null +++ b/README.md @@ -0,0 +1,382 @@ +# Implement an Automation to Deploy an OpenAPI spec into OCI API Gateway + +## Introduction + +The objective of this material is to implement an OpenAPI specification into **OCI API Gateway**. Today you can import a specification but need to deploy the APIs manually. + +The base for APIs deployment is this material [Migrate APIs to Oracle Cloud Infrastructure API Gateway with Oracle Integration](https://docs.oracle.com/en/learn/migrate-api-to-api-gateway/#introduction). +This material uses the **Oracle Integration** to deploy the APIs with a **JSON** data. + + [ { + "API_NAME" : "cep", + "TYPE" : "REST", + "METHOD" : "GET", + "PATH_PREFIX" : "/okecep", + "PATH" : "/cep", + "ENDPOINT" : "http://x.x.x.x/cep", + "QUERY_PARAMETERS" : "cep", + "GROOVY_SCRIPT" : "", + "AUTHENTICATION_TYPE" : "BASIC", + "ENVIRONMENT" : "QA", + "HEADER" : "", + "HEADER_VALUE" : "" + }, { + "API_NAME" : "calculator", + "TYPE" : "SOAP", + "METHOD" : "POST", + "PATH_PREFIX" : "/dneonline", + "PATH" : "/calculator", + "ENDPOINT" : "http://www.example.com/calculator.asmx", + "QUERY_PARAMETERS" : "", + "GROOVY_SCRIPT" : "", + "AUTHENTICATION_TYPE" : "BASIC", + "ENVIRONMENT" : "DEV", + "HEADER" : "", + "HEADER_VALUE" : "" + } ] + +The **OIC** process just deploy the APIs into **OCI API Gateway** and we need more steps. + +In this material, all functions will be deployed as an **authorizer function** inside the **OCI API Gateway**. So we can execute the services. You can make a request as a REST service with an authentication. In this examples, we can use the **Oracle IDCS** in the **OCI** with **OAuth2**. You can implement a service using this material [Use OCI API Gateway, Functions and Observability to Validate JSON Content and Monitor API Headers and Body](https://docs.oracle.com/en/learn/validate-json-content-apigw-fn/#introduction) + +The material include these services: + +- **createApi**: imports an OpenAPI specification into **OCI API Gateway** and results an **OCID** for the API imported. This information will be used in the next services to integrates the API spec with API deployments. This service also validates the API spec, but this validation will be done by the **OCI API Gateway** +- **applyValidationApi**: This service will implement the authorization and the validation following the OpenAPI spec. + + +>**Note**: All this artifacts are intended **ONLY** to be used as a reference, with no support. You need to review and refine your final code to use in a real environment. + +## CI/CD Considerations + +>**The complete process needs to be executed in 2 steps. You can execute:** +> +>**createApi + applyValidationApi** + +>**Note:** All the files to implement the services can be downloaded here: [OCI API Gateway Automation](./files/OCI_API_Gateway_Automation_files.zip) + +## OCI functions General Considerations + +All functions implement **OAuth2** with **IDCS**. + +![img.png](images/img.png) + +This is the **config** file used to configure the access to the **OCI SDK**. This file works with the **oci_api_key.pem** (the private key file to access the **OCI**). + +![img.png](images/config.png) + +And this is the **config.json** file for **IDCS** configuration. + +![img.png](images/idcs-config.png) + +In the **createApi** and **applyValidationApi**, you can work with OpenAPI and Swagger specifications in **JSON** or **YAML** format. This can be done by 2 methods in the code: + +![img.png](images/json_yaml_format.png) + +This methods will be used to mantain the specification in **JSON** default format for all codes. + +![img_1.png](images/json_yaml_format_1.png) + +First, we need to import an OpenAPI specification. The **OCI API Gateway** has a REST service to import and validate the spec. We can do it with an **OCI function createApi** . + +## createApi + +This is the first step to import an OpenAPI spec. **OCI API Gateway** has the ability to import and validate the spec but not to deploy into APIs. +The **createApi** service will create an API spec, importing your Swagger/OpenAPI spec, validate and prepare to the next step. + +![img_1.png](images/img_1.png) + +You need to deploy the API (you can see the **deployment** file [createApi.json](./files/createapi/createApi.json) in JSON format to understand the parameters): + +![img_9.png](images/img_9.png) + +![img_10.png](images/img_10.png) + +![img_11.png](images/img_11.png) + +Finally, click on **Show route response policies** and add a **Header Transformation** with: + + Behaviour=Append + Header name=api_id + Values=${request.auth[api_id]} + +![img.png](images/img_api_id.png) + +After your **createApi** deployment, you can use it. You will need an authorization token. If you configured the **IDCS OAuth2**, you can follow these instructions to deploy and obtain your OAuth2 token. [Use OCI API Gateway, Functions and Observability to Validate JSON Content and Monitor API Headers and Body](https://docs.oracle.com/en/learn/validate-json-content-apigw-fn/#introduction) + +You will need more 3 parameters: + +- **displayName**: It's your API spec name in the **OCI API Gateway** and you can choose the same name as your OpenAPI spec or not. Choose any name. +- **apiCompartmentId**: It's your **Compartment OCID** for your **OCI API Gateway** instance. You will need in the next steps +- **< YOUR OPENAPI SPEC >**: It's your **OpenAPI** spec. It could be a file or your text. + +And you can test with: + + curl --location 'https://xxxxxxxxxxxxxxxxxxxxxxx.apigateway.us-ashburn-1.oci.customer-oci.com/createApi/create' \ + --header 'token: ' \ + --header 'displayName: EXEMPLO-2024-01-01' \ + --header 'apiCompartmentId: ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \ + --header 'Content-Type: application/json' \ + --data-raw '' + +>**Note**: This step will be executed fast and you need to obtain the **api_id** to be used in the next steps. The **api_id** will be found in the **HEADER response** (not the BODY response) + +![img.png](images/api_id_response.png) + + +## authApi + +This **OCI function** will be used as an authenticator for the APIs deployments. +It authenticates with the same form used before in the services described here. The function is only an example to use, you can customize your own authorization mechanism. + +You need to deploy the function to make it available in the next steps to deploy your automation API process. + +>**Note**: After the function deployment, obtain the **OCID** to use with the service **applyValidationApi**. + +This is the **IDCS** service authorization by the token passed in BODY and will be used in your function authorization inside your API. + +![img.png](images/authApi_1.png) + +This is the main code for your authorization function and will be described in sequence. + +![img.png](images/authApi_2.png) + +Remember that the API always will be deployed in 2 layers. The first layer will call the second layer. +Authorization function works only in the second layer for best performance. This will be explained in the next section (**applyValidationApi**) + +![img_2.png](images/authApi_3.png) + +This is the schema validation for Swagger and Open API 3 + +![img.png](images/authApi_4.png) + + + +## Redact Validation Response + +The project has a schema validation from the spec. When the function is working with authentication, there is a routine for validate the body content, thanks to the open-source bravado library. + +The function send the body content and the **Swagger/Open API** specification to bravado libary and this action results in a message. There is a problem here. The results shows the content of the attributes and this need some kind of redaction. + +You can redact the attributes content with these codes. + +First, you need to import the **bravado** library for **validate_object**. + +![import-bravado.png](images/import-bravado.png) + +This code is responsible to beautify your validation response: + +![redact-code.png](images/redact-code.png) + +The **remove_property**, **replace_escape_chars** and **replace_regex** methods translate some **escape** codes inside the specification. This works as a pre-validation for redaction. + +And you can put in the main function code: + +![main-redact-routine.png](images/main-redact-routine.png) + +You need to use a library called [Redaction.py](./files/authApi/Redaction.py), included in this material. This class find your patterns for attributes that need redaction. + +Declare your patterns and attribute names: + +![redaction-2.png](images/redaction-2.png) + +So you can use the library like this: + +![redaction-1.png](images/redaction-1.png) + +When you request an REST service with this body content: + +![img.png](images/redaction-4.png) + +The response will be something like this: + +![redaction-3.png](images/redaction-3.png) + +## Resource Principal + +[Resource Principal](https://docs.oracle.com/en-us/iaas/Content/Functions/Tasks/functionsaccessingociresources.htm) is another authentication type. This type of authentication replaces the use of **config** and **private key** files and do not expose sensitive data inside your **function**. + +If you need, you can authenticate your **function** through **Resource Principal**, that let **OCI** to recognize the policy for your specific function's **OCID**, without exposing your private key or another sensitive data. +You will need to declare a **Dynamic Group** with your function **OCID** and a policy allowing the use of the **OCI** resources in your code. + +**Dynamic Group: acme-func-dyn-grp** + +![img.png](images/resourceprincipal-1.png) + +**Policy** + +![img_1.png](images/resourceprincipal-2.png) + +You need to replace your code: + +![img_2.png](images/resourceprincipal-3.png) + +with + +![img_3.png](images/resourceprincipal-4.png) + +This is the code to change: + + signer = oci.auth.signers.get_resource_principals_signer() + logging = oci.loggingingestion.LoggingClient(config={}, signer=signer) + + +See the [authRPApi.py](./files/authApi/authRPApi.py) code with the changes from **OCI Private Key** and **config** files authorization to **OCI Resource Principal** authorization. Remember to rename the **authRPApi.py** to **func.py** and build your function to test. + + +## Vault Secret + +Another way to not expose sensitive data is using **OCI Vault**. +You can configure a [Vault](https://www.ateam-oracle.com/post/using-the-oci-instance-principals-and-vault-with-python-to-retrieve-a-secret) to store your sensitive data like passwords, endpoints, etc. + +![img_4.png](images/resourceprincipal-5.png) + +You can create a **Vault** and the secrets for use in your function code: + +![img_5.png](images/vault-1.png) + +This is the code to obtain the secret value after the **base64** translation. + +![img.png](images/base64translatecode.png) + +Now, you can specify the **Secret OCID** to obtain the secret. The code are protected by **Resource Principal**. + +Declare the method to obtain and decode **base64**: + + def read_secret_value(secret_client, secret_id): + response = secret_client.get_secret_bundle(secret_id) + base64_Secret_content = response.data.secret_bundle_content.content + base64_secret_bytes = base64_Secret_content.encode('ascii') + base64_message_bytes = base64.b64decode(base64_secret_bytes) + secret_content = base64_message_bytes.decode('ascii') + return secret_content + + +Declare the initialization for your secret client: + + secret_client = oci.secrets.SecretsClient(config={}, signer=signer) + +Then you can obtain the secret value specifying your secret **OCID**: + + ClientId = read_secret_value(secret_client, "ocid1.vaultsecret.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + ClientSecret = read_secret_value(secret_client, "ocid1.vaultsecret.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + + +See the [authRPApi.py](./files/authApi/authRPApi.py) code with the changes to obtain your secrets. Remember to rename the **authRPApi.py** to **func.py** and build your function to test. + +## applyValidationApi + +The validation from OpenAPI spec is not possible today. The OCI API Gateway can implement validations with Authorization function. In the process of deploying the API from the OpenAPI spec, we can get the definitions and save it as a HEADER transformation, OCI API Gateway can do it for us, but cannot be used inside the authorization function because the HEADER transformation cannot be loaded in the function runtime execution. + +### How to resolve this issue? + +To resolve it, we need to deploy the API in 2 layers. + +The first one, we will deploy an API like a proxy to the real API. This proxy API will store the validation spec in a HEADER named **body_schema_validation**. This HEADER will be passed to the real API and can be read by the authorization function. + +The validation respecting the OpenAPI spec can be done by this component: [OpenAPI schema validator](https://pypi.org/project/openapi-schema-validator/) + +The validation respecting the Swagger 2.0 spec can be done by this component: [Swagger schema validator](https://pypi.org/project/bravado-core/) + +The authorization function is deployed in the proxy API deployment and in the real API deployment, but the validation of the spec will be done only in the real API layer and **if** the HEADER **body_schema_validation** has a content. + +![img.png](images/img_8.png) + +You need to deploy the API (you can see the **deployment** file [applyValidationApi.json](./files/applyValidationApi/applyValidationApi.json) in JSON format to understand the parameters): + +![img_17.png](images/img_17.png) + +![img.png](images/img-18e.png) + +![img_20.png](images/img_20.png) + +There are 4 news HEADER parameters: + +- **apiId**: It's your **OCI API Gateway** API specification **OCID** +- **functionId**: It's your authorization **OCI function** **OCID** mentioned in **authApi** service. +- **host_name**: It's your **OCI API Gateway** endpoint. You can find this information in the console of your instance. +- **apiGatewayId**: It's your **OCI API Gateway** deployment **OCID** to deploy your specification +- **rateLimit**: It's the rate limit configuration for the **OCI API Gateway** deployment. Put in this format seconds,[CLIENT_IP/TOTAL]. Example: 2000,CLIENT_IP (optional) + +And you can test with: + + curl --location 'https://xxxxxxxxxxxxxxxxxxxxxxx.apigateway.us-ashburn-1.oci.customer-oci.com/applyValidationApi/apply' \ + --header 'token: ' \ + --header 'apiId: ocid1.apigatewayapi.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan' \ + --header 'apiGatewayId: ocid1.apigateway.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan' \ + --header 'apiCompartmentId: ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \ + --header 'functionId: ocid1.fnfunc.oc1.iad.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \ + --header 'host_name: xxxxxxxxxxxxxxxxxxxxxxx.apigateway.us-ashburn-1.oci.customer-oci.com' \ + --header 'rateLimit: 2000,CLIENT_IP' \ + --header 'Content-Type: application/json' \ + --data-raw '' + +## API Deployment - Proxy and Real API layer + +There is a main **API Deployment** and this is created when you call the **applyValidationApi** service. + +If your OpenAPI spec has validations, your main deployment became into a **Proxy** to other layer. We can call this new layer as **Real API Layer**. + +The figure represents the **Real** layer if there is a validation for the API deployment. If no validation in the OpenAPI spec, there is no **Real** layer. +The deployment name will repeat your API Deployment name plus the **-validation** string and the **Path Index** will contains your original **Path** plus **validation-callback** string. + +There is an **authorization function** configured automatically. This configuration is in your **applyValidationApi** HEADER request. + +There is the **body_schema_validation**, **token** and **body** commented previously. + +And finally, there is a **Custom Response for Failed Auth** configuration to return the validation error. This is configured automatically by the **applyValidationApi** service. + +![img.png](images/proxy-real-config.png) + +## Conclusion + +To create an automation to: + +- Import a valid OpenAPI spec +- Deploy automatically the spec in the OCI API Gateway +- Authorize an API with a customized function +- Validate the request from the OpenAPI spec +- Validate the request from the Swagger spec + +**OCI API Gateway** needs to implement: + +- An API deployment reader from the OpenAPI spec +- Translate the path context variables +- An authorizer customized function +- A way to store the OpenAPI validation spec in each route +- A way to read the OpenAPI validation spec in runtime and validate it from the request content + +**OCI API Gateway GAP** + +- OCI API Gateway authorization function cannot read Transformations (HEADER, QUERY, BODY) +- OCI API Gateway authorization function cannot obtain the deployment_id, route path and gateway_id while the function is running +- OCI API Gateway Authorization Function with **IDCS OAuth2** validation has the limitation to 60/90 requests/second but can uses the OCI API Gateway **cache** on HEADER **token**. If you are thinking use the token cache to pass authentication, remember that the Authorization function has the Swagger/OpenAPI validation too, so the validation will not occur. The routine will bypass the validation because of the OCI API Gateway cache. + +**These services (createApi, deployApi and applyValidationApi)** + +- In the OpenAPI 3 spec, when there is no path_prefix, the service adopts the first path from the spec. Example: path=/v1/test/service ==> path_prefix will be /v1 and path will be /test/service. Not guarantee that **OCI API Gateway** release adopts this approach +- The services does not implement all specifications from Swagger and OpenAPI + +## Disclaimer + +>**IMPORTANT**: The source code must be used at your own risk. There is no support and/or link with any company. The source code is free to modify and was built solely for the purpose of helping the community + +## Acknowledgments + +- Author: Cristiano Hoshikawa (Oracle LAD A-Team Solution Engineer) + +## Source-Code + +- [Source Code for OCI API Gateway Automation](./files/OCI_API_Gateway_Automation_files.zip) + +## References + +- [Migrate APIs to Oracle Cloud Infrastructure API Gateway with Oracle Integration](https://docs.oracle.com/en/learn/migrate-api-to-api-gateway/#introduction) +- [Use OCI API Gateway, Functions and Observability to Validate JSON Content and Monitor API Headers and Body](https://docs.oracle.com/en/learn/validate-json-content-apigw-fn/#introduction) +- [OpenAPI schema validator](https://pypi.org/project/openapi-schema-validator/) +- [Swagger schema validator](https://pypi.org/project/bravado-core/) +- [Adding Context Variables to Policies and HTTP Back End Definitions](https://docs.oracle.com/en-us/iaas/Content/APIGateway/Tasks/apigatewaycontextvariables.htm) +- [IDCS API Rate Limits](https://docs.oracle.com/en/cloud/paas/identity-cloud/uaids/oracle-identity-cloud-service-pricing-models.html#GUID-C1505A67-9C21-484A-8395-04C4253FA1CD) +- [Create Policies to Control Access to Network and API Gateway-Related Resources](https://docs.oracle.com/en-us/iaas/Content/APIGateway/Tasks/apigatewaycreatingpolicies.htm) +- [SDK Authentication Methods](https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdk_authentication_methods.htm) +- [Using the OCI Instance Principals and Vault with Python to retrieve a Secret](https://www.ateam-oracle.com/post/using-the-oci-instance-principals-and-vault-with-python-to-retrieve-a-secret) diff --git a/files/.DS_Store b/files/.DS_Store new file mode 100644 index 0000000..6fce2ce Binary files /dev/null and b/files/.DS_Store differ diff --git a/files/OCI_API_Gateway_Automation_files.zip b/files/OCI_API_Gateway_Automation_files.zip new file mode 100644 index 0000000..4fe88eb Binary files /dev/null and b/files/OCI_API_Gateway_Automation_files.zip differ diff --git a/files/applyValidationApi/applyValidationApi.json b/files/applyValidationApi/applyValidationApi.json new file mode 100644 index 0000000..85e7c11 --- /dev/null +++ b/files/applyValidationApi/applyValidationApi.json @@ -0,0 +1,101 @@ +{ + "compartment_id": "", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "oracleidentitycloudservice/cristiano.hoshikawa@oracle.com", + "CreatedOn": "2024-01-04T14:21:31.582Z" + } + }, + "display_name": "applyValidationApi", + "endpoint": "https://aaaaaaaaaaaaaaaaaaaaaaaaaa.apigateway.us-ashburn-1.oci.customer-oci.com/applyValidationApi", + "freeform_tags": {}, + "gateway_id": "ocid1.apigateway.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + "id": "ocid1.apideployment.oc1.iad.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "lifecycle_details": null, + "lifecycle_state": "ACTIVE", + "path_prefix": "/applyValidationApi", + "specification": { + "logging_policies": { + "access_log": null, + "execution_log": { + "is_enabled": null, + "log_level": "INFO" + } + }, + "request_policies": { + "authentication": { + "cache_key": [ + "apiId", + "apiCompartmentId", + "functionId", + "host", + "token", + "apiGatewayId" + ], + "function_id": "", + "is_anonymous_access_allowed": false, + "parameters": { + "apiCompartmentId": "request.headers[apiCompartmentId]", + "apiId": "request.headers[apiId]", + "body": "request.body", + "functionId": "request.headers[functionId]", + "host": "request.host", + "token": "request.headers[token]", + "apiGatewayId": "request.headers[apiGatewayId]", + "rateLimit": "request.headers[rateLimit]" + }, + "token_header": null, + "token_query_param": null, + "type": "CUSTOM_AUTHENTICATION", + "validation_failure_policy": null + }, + "cors": null, + "dynamic_authentication": null, + "mutual_tls": { + "allowed_sans": [], + "is_verified_certificate_required": false + }, + "rate_limiting": null, + "usage_plans": null + }, + "routes": [ + { + "backend": { + "body": "{\"status\": \"success\"}", + "headers": [], + "status": 200, + "type": "STOCK_RESPONSE_BACKEND" + }, + "logging_policies": { + "access_log": null, + "execution_log": { + "is_enabled": null, + "log_level": null + } + }, + "methods": [ + "POST" + ], + "path": "/apply", + "request_policies": { + "authorization": { + "type": "AUTHENTICATION_ONLY" + }, + "body_validation": null, + "cors": null, + "header_transformations": null, + "header_validations": null, + "query_parameter_transformations": null, + "query_parameter_validations": null, + "response_cache_lookup": null + }, + "response_policies": { + "header_transformations": null, + "response_cache_store": null + } + } + ] + }, + "time_created": "2024-01-04T14:21:31.831000+00:00", + "time_updated": "2024-01-04T14:31:48.792000+00:00" +} diff --git a/files/applyValidationApi/config b/files/applyValidationApi/config new file mode 100644 index 0000000..74fb1cd --- /dev/null +++ b/files/applyValidationApi/config @@ -0,0 +1,7 @@ +[DEFAULT] +user=ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +fingerprint=36:04:63:cd:36:04:63:cd:36:04:63:cd:36:04:63:cd +key_file=oci_api_key.pem +tenancy=ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +region=us-ashburn-1 + diff --git a/files/applyValidationApi/config.json b/files/applyValidationApi/config.json new file mode 100644 index 0000000..cfdb527 --- /dev/null +++ b/files/applyValidationApi/config.json @@ -0,0 +1,12 @@ +{ + "ClientId" : "0b8cd92bb60b8cd92bb60b8cd92bb6", + "ClientSecret" : "41964196-2cfb-2cfb-2cfb-63246a63246a", + "BaseUrl" : "https://idcs-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.identity.oraclecloud.com", + "AudienceServiceUrl" : "https://idcs-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.identity.oraclecloud.com", + "scope" : "aaaaaaaaaaaaaaaaaaaaaaa.apigateway.us-ashburn-1.oci.customer-oci.com/super_scope", + "TokenIssuer" : "https://identity.oraclecloud.com", + "redirectURL": "http://localhost:8000/callback", + "logoutSufix":"/oauth2/v1/userlogout", + "LogLevel":"INFO", + "ConsoleLog":"True" +} \ No newline at end of file diff --git a/files/applyValidationApi/func.py b/files/applyValidationApi/func.py new file mode 100644 index 0000000..7e86b9c --- /dev/null +++ b/files/applyValidationApi/func.py @@ -0,0 +1,710 @@ +import base64 +import json +import io +from fdk import response +import oci +import requests +import time +from itertools import groupby +import yaml +import datetime +import ast + +#### IDCS Routines +#### https://docs.oracle.com/en/learn/apigw-modeldeployment/index.html#introduction +#### https://docs.oracle.com/en/learn/migrate-api-to-api-gateway/#introduction + +def auth_idcs(token, url, clientID, secretID): + url = url + "/oauth2/v1/introspect" + + auth = clientID + ":" + secretID + auth_bytes = auth.encode("ascii") + auth_base64_bytes = base64.b64encode(auth_bytes) + auth_base64_message = auth_base64_bytes.decode("ascii") + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + auth_base64_message + } + + payload = "token=" + token + + response = requests.request("POST", url, headers=headers, data=payload) + return response + +#Function used to load the configurations from the config.json file +def getOptions(): + fo = open("config.json", "r") + config = fo.read() + options = json.loads(config) + return options + +### OCI API Gateway Migration Routines + +def find_base_path(strPath): + base_path = strPath.split('/')[1] + if (len(base_path) == 0): + base_path = strPath + else: + base_path = "/" + base_path + return base_path + +def has_path_endpoint(endPoint): + endPointAux = endPoint.replace("//", "#") + endPointSplited = endPointAux.split('/') + if (len(endPointSplited) > 1): + return True + else: + return False + +def concatSplited(endPointSplited): + count = 0 + endPointStr = "" + for item in endPointSplited: + if (count > 0): + endPointStr = endPointStr + "/" + item + count = count + 1 + return endPointStr + +def find_base_pathendpoint(endPoint, strPath): + base_path = strPath.split('/')[1] + if (len(base_path) == 0 and has_path_endpoint(endPoint)): + endPointAux = endPoint.replace("//", "#") + endPointSplited = endPointAux.split('/') + if (len(endPointSplited) > 1): + endPointSplitedStr = concatSplited(endPointSplited) + if (endPointSplitedStr != None): + base_path = endPointSplitedStr + else: + base_path = strPath + else: + base_path = strPath + else: + endPointAux = endPoint.replace("//", "#") + endPointSplited = endPointAux.split('/') + if (len(endPointSplited) > 1): + endPointSplitedStr = concatSplited(endPointSplited) + if (endPointSplitedStr != None): + base_path = endPointSplitedStr + endPoint = endPointSplited[0].replace("#", "//") + else: + base_path = "/" + base_path + else: + base_path = "/" + base_path + return base_path + + +def find_base_endpoint(endPoint): + endPointAux = endPoint.replace("//", "#") + endPointSplited = endPointAux.split('/') + if (len(endPointSplited) > 1): + endPointSplitedStr = endPointSplited[1] + if (endPointSplitedStr != None): + endPoint = endPointSplited[0].replace("#", "//") + return endPoint + +def find_path(strPath): + base_path = strPath.split('/') + if (len(base_path) == 0): + return strPath + else: + auxPath = "" + skipCount = 0 + for b in base_path: + if (skipCount > 1): + auxPath = auxPath + "/" + b + skipCount = skipCount + 1 + base_path = auxPath + return auxPath + +def removeLastSlash(path): + return path.rstrip("/") + +def creeateOrUpdateDeployment(compartmendId, displayName, validation_deployment_details, create_deployment_details, api_gateway_id): + config = oci.config.from_file("config") + apigateway_client = oci.apigateway.DeploymentClient(config) + listGateway = apigateway_client.list_deployments(compartment_id=compartmendId, display_name=displayName, lifecycle_state="ACTIVE") + gateway = json.loads(str(listGateway.data)) + ind = -1 + c = -1 + if (len(gateway) > 0): + c = 0 + for item in gateway["items"]: + if (item["gateway_id"] == api_gateway_id): + ind = c + break + c = c + 1 + if (gateway["items"] != [] and c > -1 and ind > -1): + gateway_id = gateway["items"][ind]["gateway_id"] + deployment_id = gateway["items"][ind]["id"] + else: + gateway_id = api_gateway_id + deployment_id = "" + + if (gateway["items"] != [] and deployment_id != ""): + apigateway_client.update_deployment(deployment_id=deployment_id, update_deployment_details=validation_deployment_details) + else: + apigateway_client.create_deployment(create_deployment_details=create_deployment_details) + +def applyAuthApi(compartmentId, displayName, payload, functionId, host, api_gateway_id, rate_limit): + config = oci.config.from_file("config") + logging = oci.loggingingestion.LoggingClient(config) + apigateway_client = oci.apigateway.DeploymentClient(config) + listGateway = apigateway_client.list_deployments(compartment_id=compartmentId, display_name=displayName, lifecycle_state="ACTIVE") + gateway = json.loads(str(listGateway.data)) + ind = -1 + c = -1 + if (len(gateway) > 0): + c = 0 + for item in gateway["items"]: + if (item["gateway_id"] == api_gateway_id): + ind = c + break + c = c + 1 + if (gateway["items"] != [] and c > -1 and ind > -1): + gateway_id = gateway["items"][ind]["gateway_id"] + deployment_id = gateway["items"][ind]["id"] + else: + gateway_id = api_gateway_id + deployment_id = 0 + + try: + rate_config = rate_limit.split(',') + rate_seconds = int(rate_config[0]) + rate_key = rate_config[1] + rate_limiting = oci.apigateway.models.RateLimitingPolicy( + rate_in_requests_per_second=rate_seconds, + rate_key=rate_key) + except: + rate_limiting = None + + path_prefix = "/" + routes = [ ] + new_routes = [ ] + for item in payload: + methods = [item["METHOD"]] + path_prefix = item["PATH_PREFIX"] + callback_url = ("https://" + host + item["PATH_PREFIX"] + "validation-callback" + item["PATH"]).replace("{", "${request.path[").replace("}", "]}") + item_policy = [] + item_query = [] + item_header = [] + if (item["SCHEMA_BODY_VALIDATION"] != ""): + item_policy.append(oci.apigateway.models.SetHeaderPolicyItem( + name="body_schema_validation", + values=[item["SCHEMA_BODY_VALIDATION"]], + if_exists="APPEND")) + if (item["SCHEMA_QUERY_VALIDATION"] != ""): + item_policy.append(oci.apigateway.models.SetHeaderPolicyItem( + name="query_schema_validation", + values=[item["SCHEMA_QUERY_VALIDATION"]], + if_exists="APPEND")) + try: + for items in ast.literal_eval(item["SCHEMA_QUERY_VALIDATION"]): + if (items["in"] == "query"): + item_query.append(oci.apigateway.models.QueryParameterValidationItem( + name=items["name"], + required=items["required"] + )) + if (items["in"] == "header"): + item_header.append(oci.apigateway.models.HeaderValidationItem( + name=items["name"], + required=items["required"] + )) + except: + print("NO") + + if (item["SCHEMA_BODY_VALIDATION"] != "" or item["SCHEMA_QUERY_VALIDATION"] != ""): + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="callback_url: " + callback_url, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + header_transformation = None + query_parameter_validation = None + header_validation = None + if (len(item_policy) >0): + header_transformation=oci.apigateway.models.HeaderTransformationPolicy( + set_headers=oci.apigateway.models.SetHeaderPolicy( + items=item_policy)) + if (len(item_query) > 0): + query_parameter_validation=oci.apigateway.models.QueryParameterValidationRequestPolicy( + parameters=item_query + ) + if (len(item_header) > 0): + header_validation=oci.apigateway.models.HeaderValidationRequestPolicy( + headers=item_header + ) + + routes.append( + oci.apigateway.models.ApiSpecificationRoute( + path=item["PATH"], + backend=oci.apigateway.models.HTTPBackend( + type="HTTP_BACKEND", + url=callback_url, + is_ssl_verify_disabled=False), + methods=methods, + request_policies=oci.apigateway.models.ApiSpecificationRouteRequestPolicies( + header_transformations=header_transformation, + query_parameter_validations=query_parameter_validation, + header_validations=header_validation + ))) + new_routes.append( + oci.apigateway.models.ApiSpecificationRoute( + path=item["PATH"], + backend=oci.apigateway.models.HTTPBackend( + type="HTTP_BACKEND", + url=item["ENDPOINT"], + is_ssl_verify_disabled=False), + methods=methods, + request_policies=oci.apigateway.models.ApiSpecificationRouteRequestPolicies( + header_transformations=header_transformation, + query_parameter_validations=query_parameter_validation, + header_validations=header_validation + ) + )) + else: + routes.append( + oci.apigateway.models.ApiSpecificationRoute( + path=item["PATH"], + backend=oci.apigateway.models.HTTPBackend( + type="HTTP_BACKEND", + url=callback_url, + is_ssl_verify_disabled=False), + methods=methods)) + new_routes.append( + oci.apigateway.models.ApiSpecificationRoute( + path=item["PATH"], + backend=oci.apigateway.models.HTTPBackend( + type="HTTP_BACKEND", + url=item["ENDPOINT"], + is_ssl_verify_disabled=False), + methods=methods)) + + + if (new_routes != [ ]): + validation_deployment_details=oci.apigateway.models.UpdateDeploymentDetails( + display_name=displayName + "-validation", + specification=oci.apigateway.models.ApiSpecification( + request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies( + rate_limiting=rate_limiting, + authentication=oci.apigateway.models.CustomAuthenticationPolicy( + type="CUSTOM_AUTHENTICATION", + function_id=functionId, + is_anonymous_access_allowed=False, + parameters={ + 'token': 'request.headers[token]', + 'body': 'request.body', + 'body_schema_validation': 'request.headers[body_schema_validation]', + 'query_schema_validation': 'request.headers[query_schema_validation]', + 'opc-request-id': 'request.headers[opc-request-id]'}, + cache_key=["token", "opc-request-id"], + validation_failure_policy=oci.apigateway.models.ModifyResponseValidationFailurePolicy( + type="MODIFY_RESPONSE", + response_code="401", + response_message="${request.auth[error]}" + ) + )), + routes=new_routes)) + create_deployment_details=oci.apigateway.models.CreateDeploymentDetails( + display_name=displayName + "-validation", + compartment_id=compartmentId, + gateway_id=gateway_id, + path_prefix= path_prefix + "validation-callback", + specification=oci.apigateway.models.ApiSpecification( + request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies( + rate_limiting=rate_limiting, + authentication=oci.apigateway.models.CustomAuthenticationPolicy( + type="CUSTOM_AUTHENTICATION", + function_id=functionId, + is_anonymous_access_allowed=False, + parameters={ + 'token': 'request.headers[token]', + 'body': 'request.body', + 'body_schema_validation': 'request.headers[body_schema_validation]', + 'query_schema_validation': 'request.headers[query_schema_validation]', + 'opc-request-id': 'request.headers[opc-request-id]'}, + cache_key=["token", "opc-request-id"], + validation_failure_policy=oci.apigateway.models.ModifyResponseValidationFailurePolicy( + type="MODIFY_RESPONSE", + response_code="401", + response_message="${request.auth[error]}" + ) + )), + routes=new_routes)) + creeateOrUpdateDeployment(compartmendId=compartmentId, displayName=displayName + "-validation", validation_deployment_details=validation_deployment_details, create_deployment_details=create_deployment_details, api_gateway_id=api_gateway_id) + + if (routes != [ ]): + # The 1st layer will not authenticate + validation_deployment_details=oci.apigateway.models.UpdateDeploymentDetails( + display_name=displayName, + specification=oci.apigateway.models.ApiSpecification( + request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies( + rate_limiting=rate_limiting), + routes=routes)) + + create_deployment_details=oci.apigateway.models.CreateDeploymentDetails( + display_name=displayName, + compartment_id=compartmentId, + gateway_id=gateway_id, + path_prefix= path_prefix, + specification=oci.apigateway.models.ApiSpecification( + request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies( + rate_limiting=rate_limiting), + routes=routes)) + + creeateOrUpdateDeployment(compartmendId=compartmentId, displayName=displayName, validation_deployment_details=validation_deployment_details, create_deployment_details=create_deployment_details, api_gateway_id=api_gateway_id) + +def check_endpoint(schemes, endpoint): + if (schemes == ""): + if (endpoint.find("http://") == -1 and endpoint.find("https://") == -1): + endpoint = "https://" + endpoint + else: + if (endpoint.find("http://") == -1 and endpoint.find("https://") == -1): + if (schemes.find("://") == -1): + endpoint = schemes + "://" + endpoint + else: + endpoint = schemes + endpoint + return endpoint + +def key_func(k): + return k['PATH'] + +def verify_path(json_data_list): + list_final = [] + for item in json_data_list: + if (item["PATH"] == ""): + for item2 in json_data_list: + if (item2["PATH"] == ""): + list_final.append({ + 'API_NAME': item2["API_NAME"], + 'TYPE': item2["TYPE"], + 'ENVIRONMENT': item2["ENVIRONMENT"], + 'METHOD': item2["METHOD"], + 'PATH_PREFIX': "/", + 'PATH': item2["PATH_PREFIX"], + 'ENDPOINT': item2["ENDPOINT"], + 'SCHEMA_BODY_VALIDATION': item2["SCHEMA_BODY_VALIDATION"], + 'SCHEMA_QUERY_VALIDATION': item2["SCHEMA_QUERY_VALIDATION"], + 'CONTENT_TYPE': item2["CONTENT_TYPE"] + }) + else: + list_final.append({ + 'API_NAME': item2["API_NAME"], + 'TYPE': item2["TYPE"], + 'ENVIRONMENT': item2["ENVIRONMENT"], + 'METHOD': item2["METHOD"], + 'PATH_PREFIX': item2["PATH_PREFIX"], + 'PATH': item2["PATH"], + 'ENDPOINT': item2["ENDPOINT"], + 'SCHEMA_BODY_VALIDATION': item2["SCHEMA_BODY_VALIDATION"], + 'SCHEMA_QUERY_VALIDATION': item2["SCHEMA_QUERY_VALIDATION"], + 'CONTENT_TYPE': item2["CONTENT_TYPE"] + }) + + return list_final + return json_data_list + +def process_api_spec(api_id, compartmentId, environment, swagger, functionId, host, api_gateway_id, rate_limit): + type = "REST" + config = oci.config.from_file("config") + apigateway_client = oci.apigateway.ApiGatewayClient(config) + logging = oci.loggingingestion.LoggingClient(config) + #----------------------------------------------------------------- + try: + data = swagger + fullSpec = json.loads(data) + + version = "3" + try: + version = (fullSpec["swagger"])[:1] + except: + version = (fullSpec["openapi"])[:1] + + print("version", version) + + if (version == "3"): + endPoint = fullSpec["servers"][0]["url"] + else: + endPoint = fullSpec["host"] + + get_api = apigateway_client.get_api_deployment_specification(api_id=api_id, opc_request_id="DEPLOY-0001") + + api_spec = json.loads(str(get_api.data)) + + json_data_list = [] + + endPointOrigin = endPoint + for spec in api_spec["routes"]: + status = spec["backend"]["status"] + specPath = spec["path"] + + for method in spec["methods"]: + METHOD = method.lstrip().upper() + CONTENT_TYPE = "" + + if (version == "3"): + if (has_path_endpoint(endPointOrigin)): + endPoint = find_base_endpoint(endPointOrigin) + specPath = (find_base_pathendpoint(endPointOrigin, specPath)).replace("//", "/") + fullEndpoint = (endPoint + specPath + spec["path"]).replace("{", "${request.path[").replace("}", "]}") + FULL_PATH = specPath + ENDPOINT = fullEndpoint + PATH = spec["path"] + PATH_PREFIX = specPath + else: + fullEndpoint = (endPoint + find_base_path(specPath) + find_path(specPath)).replace("{", "${request.path[").replace("}", "]}") + FULL_PATH = specPath + ENDPOINT = fullEndpoint + PATH = find_path(specPath) + PATH_PREFIX = find_base_path(specPath) + else: + schemes = "" + try: + schemes = fullSpec["schemes"][0] + except: + schemes = "https" + + fullEndpoint = check_endpoint(schemes, (endPoint + removeLastSlash(fullSpec["basePath"]) + spec["path"]).replace("{", "${request.path[").replace("}", "]}")) + FULL_PATH = fullSpec["basePath"] + spec["path"] + ENDPOINT = fullEndpoint + PATH = spec["path"] + PATH_PREFIX = removeLastSlash(fullSpec["basePath"]) + + OPERATIONID = fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["operationId"] + API_NAME = fullSpec["info"]["title"] + if (version == "3"): + try: + try: + reference = str(fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["requestBody"]["content"]["application/json"]["schema"]["$ref"]).replace("#/components/schemas/", "") + SCHEMA_BODY_VALIDATION = reference + "," + api_id + except: + reference = str(fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["requestBody"]["content"]["application/json"]) + SCHEMA_BODY_VALIDATION = reference + CONTENT_TYPE = "application/json" + except: + SCHEMA_BODY_VALIDATION = "" + else: + SCHEMA_BODY_VALIDATION = "" + try: + reference = str(fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["parameters"][0]["schema"]["$ref"]).replace("#/definitions/", "") + SCHEMA_BODY_VALIDATION = reference + "," + api_id + CONTENT_TYPE = "application/json" + except: + SCHEMA_BODY_VALIDATION = "" + + # 2024-06-26 - Query Parameter + if (version == "3"): + try: + try: + reference = str(fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["parameters"]) + SCHEMA_QUERY_VALIDATION = reference #+ "," + api_id + except: + reference = str(fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["parameters"]) + SCHEMA_QUERY_VALIDATION = reference + CONTENT_TYPE = "application/json" + except: + SCHEMA_QUERY_VALIDATION = "" + else: + SCHEMA_QUERY_VALIDATION = "" + try: + reference = str(fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["parameters"]) + SCHEMA_QUERY_VALIDATION = reference #+ "," + api_id + CONTENT_TYPE = "application/json" + except: + SCHEMA_QUERY_VALIDATION = "" + + TYPE = type + ENVIRONMENT = environment + json_data_list.append({ + 'API_NAME': API_NAME, + 'TYPE': TYPE, + 'ENVIRONMENT': ENVIRONMENT, + 'METHOD': METHOD, + 'PATH_PREFIX': PATH_PREFIX, + 'PATH': PATH, + 'ENDPOINT': ENDPOINT, + 'SCHEMA_BODY_VALIDATION': SCHEMA_BODY_VALIDATION, + 'SCHEMA_QUERY_VALIDATION': SCHEMA_QUERY_VALIDATION, + 'CONTENT_TYPE': CONTENT_TYPE + }) + + print(API_NAME, TYPE, ENVIRONMENT, METHOD, PATH_PREFIX, PATH, ENDPOINT, SCHEMA_BODY_VALIDATION, SCHEMA_QUERY_VALIDATION, CONTENT_TYPE) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="api deployment: " + json.dumps({ + 'API_NAME': API_NAME, + 'TYPE': TYPE, + 'ENVIRONMENT': ENVIRONMENT, + 'METHOD': METHOD, + 'PATH_PREFIX': PATH_PREFIX, + 'PATH': PATH, + 'ENDPOINT': ENDPOINT, + 'SCHEMA_BODY_VALIDATION': SCHEMA_BODY_VALIDATION, + 'SCHEMA_QUERY_VALIDATION': SCHEMA_QUERY_VALIDATION, + 'CONTENT_TYPE': CONTENT_TYPE + }), + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + + json_data_list = verify_path(json_data_list) + payload = json.dumps(json_data_list) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="json_data_list: " + payload, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + payload = json.loads(json.dumps(json_data_list)) + print(payload) + applyAuthApi(compartmentId=compartmentId, displayName=API_NAME, payload=payload, functionId=functionId, host=host, api_gateway_id=api_gateway_id, rate_limit=rate_limit) + + except(Exception) as ex: + raise + +def DateEncoder(obj): + if isinstance(obj, datetime.datetime): + return obj.strftime('%Y-%m-%d') + +def is_json(swagger): + try: + body = json.loads(swagger) + return True + except: + try: + yaml_object = yaml.safe_load(swagger) # yaml_object will be a list or a dict + s = json.dumps(yaml_object, indent=2, default=DateEncoder) + return False + except: + return False + +def convert_json(swagger): + yaml_object = yaml.safe_load(swagger) # yaml_object will be a list or a dict + return json.dumps(yaml_object, indent=2, default=DateEncoder) + + +### + +def handler(ctx, data: io.BytesIO = None): + config = oci.config.from_file("config") + logging = oci.loggingingestion.LoggingClient(config) + + # functions context variables + app_context = dict(ctx.Config()) + + jsonData = "" + + options = getOptions() + + try: + header = json.loads(data.getvalue().decode('utf-8'))["data"] + url = options["BaseUrl"] + body = dict(json.loads(data.getvalue().decode('utf-8')).get("data"))["body"] + # body content + swagger = str(body) + if (is_json(swagger)): + body = json.loads(body) + else: + body = json.loads(convert_json(swagger)) + swagger = convert_json(swagger) + + environment = "DEV" + + # header values + access_token = header["token"] + api_id = header["apiId"] + host = header["host_name"] + compartmentId = header['apiCompartmentId'] + functionId = header['functionId'] + api_gateway_id = header['apiGatewayId'] + rate_limit = header['rateLimit'] + + authorization = auth_idcs(access_token, url, options["ClientId"], options["ClientSecret"]) + try: + if (authorization.json().get("active") != True): + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) + except(Exception) as ex1: + jsonData = 'error parsing json payload: ' + str(ex1) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(1): " + jsonData, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) + + # Create API spec + process_api_spec(api_id=api_id, compartmentId=compartmentId, environment=environment, swagger=swagger, functionId=functionId, host=host, api_gateway_id=api_gateway_id, rate_limit=rate_limit) + + rdata = json.dumps({ + "active": True, + "context": { + "environment": environment, + "api_id": api_id + }}) + + return response.Response( + ctx, response_data=rdata, + status_code=200, + headers={"Content-Type": "application/json", "apiId": api_id, "environment": environment} + ) + + except(Exception) as ex: + jsonData = 'error parsing json payload: ' + str(ex) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(2): " + jsonData, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + pass + + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) \ No newline at end of file diff --git a/files/applyValidationApi/func.yaml b/files/applyValidationApi/func.yaml new file mode 100644 index 0000000..6f60aed --- /dev/null +++ b/files/applyValidationApi/func.yaml @@ -0,0 +1,8 @@ +schema_version: 20180708 +name: apply-validation-api +version: 0.0.512 +runtime: python +build_image: fnproject/python:3.9-dev +run_image: fnproject/python:3.9 +entrypoint: /python/bin/fdk /function/func.py handler +memory: 256 diff --git a/files/applyValidationApi/oci_api_key.pem b/files/applyValidationApi/oci_api_key.pem new file mode 100644 index 0000000..2dba649 --- /dev/null +++ b/files/applyValidationApi/oci_api_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK ++PlyICemh7G2GNTwleCu61CVYaVcXxZG8LQkHAHCykuq+R7d6lwxkHQTIyKmUj+o +6BCWIQKBgQCAHaQZ4p/XDHVqajity2YCauQKM7eD0cyUnY9h+MKkih1g7brU43DR +u1yJoOnQzddapVr7yVXMl874mU+Jgm7arh+XRL8WuV2RtltKurBhYqtSwiGg0JFx +pRZm1D73NtXRaTSSwYdXakQjPb4FaFdwBouxVylP6GSy4kI2iva3og== +-----END RSA PRIVATE KEY----- diff --git a/files/applyValidationApi/requirements.txt b/files/applyValidationApi/requirements.txt new file mode 100644 index 0000000..500c823 --- /dev/null +++ b/files/applyValidationApi/requirements.txt @@ -0,0 +1,9 @@ +fdk>=0.1.54 +requests +oci +cryptography +six +PyJWT +py3_lru_cache +simplejson +PyYAML diff --git a/files/authApi/Redaction.py b/files/authApi/Redaction.py new file mode 100644 index 0000000..8df83bf --- /dev/null +++ b/files/authApi/Redaction.py @@ -0,0 +1,169 @@ +import re + +# This class does not work with numeric attributes, only with String +# Use the method redact and put your patterns as the example: +# SENSITIVE_PATTERNS = [ +# r"\d{3}-\d{2}-\d{4}", # Social Security Number (SSN) pattern +# r"\d{4}[-\s]\d{4}[-\s]\d{4}[-\s]\d{4}", # Credit card number pattern +# r"\(?\d{3}\)?[-\s.]?\d{3}[-\s.]?\d{4}", # Phone number +# r"(0[1-9]|1[0-2])[-/.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d", # date of birth +# r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)", # IP address +# r"[a-zA-Z0-9]{32}" # API key +# ] +# +# ATTRIBUTE_PATTERNS = [ +# "nome", +# "cpf", +# "teste", +# "valor", +# "original", +# "type", +# "solicitacaoPagador", +# "chave", +# "description", +# "items", +# "example" +# ] + +class Redaction(): + + def repl_value2(self, message, pattern): + flag_aspas = False + flag_attribute = False + # flag_vezes = 0 + flag_dois_pontos = False + flag_colchetes = False + i = 0 + z = pattern + str_acc = "" + while (i < len(message)): + try: + if (message[i:i + len(z)] == z and (message[i + len(z):i + len(z) + 1] == "'" or message[i + len(z):i + len(z) + 1] == "\"")): + flag_attribute = True + flag_aspas = True + except: + print("except") + if (message[i] == ":" and not flag_aspas and flag_attribute): + flag_dois_pontos = True + if (flag_aspas and message[i] != "'" and message[i] != "\"" and flag_attribute and flag_dois_pontos): + str_acc = str_acc + message[i] + message = message[0:i] + "*" + message[i + 1:len(message) + i] + if (message[i] == "{" and flag_dois_pontos and not flag_aspas): + flag_attribute = False + flag_dois_pontos = False + flag_aspas = False + flag_colchetes = False + if ((message[i] == "}" or message[i] == "]") and not flag_aspas): + flag_attribute = False + flag_dois_pontos = False + flag_aspas = False + flag_colchetes = False + str_acc = "" + if (flag_dois_pontos and not flag_aspas and message[i] == "["): + flag_colchetes = True + if (message[i] == "," and not flag_aspas and not flag_colchetes): + flag_attribute = False + flag_dois_pontos = False + flag_aspas = False + flag_colchetes = False + str_acc = "" + if ((message[i] == "'" or message[i] == "\"")): + flag_aspas = not flag_aspas + if (flag_aspas == False and flag_attribute == True and flag_dois_pontos and len(str_acc) > 0 and not flag_colchetes): + flag_attribute = False + flag_dois_pontos = False + str_acc = "" + i = i + 1 + return message + + def repl_value(self, message, pattern): + flag_aspas = False + flag_attribute = False + # flag_vezes = 0 + flag_dois_pontos = False + flag_colchetes = False + flag_string = True + flag_descobre_tipo = False + i = 0 + z = pattern + str_acc = "" + while (i < len(message)): + try: + if (message[i:i + len(z)] == z and (message[i + len(z):i + len(z) + 1] == "'" or message[i + len(z):i + len(z) + 1] == "\"")): + flag_attribute = True + flag_aspas = True + except: + print("except") + if (message[i] == ":" and not flag_aspas and flag_attribute): + flag_dois_pontos = True + flag_descobre_tipo = True + if ((flag_aspas and message[i] != "'" and message[i] != "\"" and flag_attribute and flag_dois_pontos) + or (message[i] in "0123456789." and flag_attribute and flag_dois_pontos and not flag_string)): + str_acc = str_acc + message[i] + message = message[0:i] + "*" + message[i + 1:len(message) + i] + if (message[i] == "{" and flag_dois_pontos and not flag_aspas): + flag_attribute = False + flag_dois_pontos = False + flag_aspas = False + flag_colchetes = False + flag_descobre_tipo = False + flag_string = True + if ((message[i] == "}" or message[i] == "]") and not flag_aspas): + flag_attribute = False + flag_dois_pontos = False + flag_aspas = False + flag_colchetes = False + flag_descobre_tipo = False + flag_string = True + str_acc = "" + if ((message[i] == "}" or message[i] == "]") and not flag_aspas and not flag_string): + flag_attribute = False + flag_dois_pontos = False + flag_aspas = False + flag_colchetes = False + flag_descobre_tipo = False + flag_string = True + str_acc = "" + if (flag_dois_pontos and not flag_aspas and message[i] == "["): + flag_colchetes = True + if (message[i] == "," and not flag_aspas and not flag_colchetes and not flag_string): + flag_attribute = False + flag_dois_pontos = False + flag_aspas = False + flag_colchetes = False + flag_descobre_tipo = False + flag_string = True + str_acc = "" + if ((message[i] == "'" or message[i] == "\"")): + flag_aspas = not flag_aspas + if (flag_descobre_tipo): + flag_string = True + flag_descobre_tipo = False + if (message[i] in "01234567890." and flag_descobre_tipo): + flag_string = False + flag_descobre_tipo = False + str_acc = str_acc + message[i] + message = message[0:i] + "*" + message[i + 1:len(message) + i] + if (flag_aspas == False and flag_attribute == True and flag_dois_pontos and len(str_acc) > 0 and not flag_colchetes and flag_string): + flag_attribute = False + flag_dois_pontos = False + flag_descobre_tipo = False + str_acc = "" + i = i + 1 + return message + + def repl(self, attribute_pattern, message): + msg_return = [] + for pattern in attribute_pattern: + message = self.repl_value(message, pattern) + return message + + def change(self, sensitive_pattern, message): + for pattern in sensitive_pattern: + message = re.sub(pattern, "", message) + return message + + def redact(self, sensitive_pattern, attribute_pattern, message): + message = self.repl(attribute_pattern, message) + message = self.change(sensitive_pattern, message) + return message diff --git a/files/authApi/authRPApi.py b/files/authApi/authRPApi.py new file mode 100644 index 0000000..7ef1fc4 --- /dev/null +++ b/files/authApi/authRPApi.py @@ -0,0 +1,348 @@ +import base64 +import json +import io +from fdk import response +import oci +import requests +import time +from openapi_schema_validator import validate +import os +import ast +from bravado_core.spec import Spec +from bravado_core.validate import validate_object +from datetime import datetime +from random import randrange + +import Redaction + +SENSITIVE_PATTERNS = [ + r"\d{3}-\d{2}-\d{4}", # Social Security Number (SSN) pattern + r"\d{4}[-\s]\d{4}[-\s]\d{4}[-\s]\d{4}", # Credit card number pattern + r"\(?\d{3}\)?[-\s.]?\d{3}[-\s.]?\d{4}", # Phone number + r"(0[1-9]|1[0-2])[-/.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d", # date of birth + r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)", # IP address + r"[a-zA-Z0-9]{32}", # API key + r"^(\d{5}.\d{2}-\d)|(\d{8})$" +] + +ATTRIBUTE_PATTERNS = [ + "documentNumber", + "documentCustodyAgentAccountCode", + "isinCode", + "payingAgentAccountCode", + "registrationParticipantAccountCode", + "nome", + "$ref", + "cpf", + "teste", + "valor", + "original", + "type", + "solicitacaoPagador", + "expiracao", + "chave", + "description", + "items", + "required", + "x-scope", + "maxLength" +] + +#### IDCS Routines +#### https://docs.oracle.com/en/learn/apigw-modeldeployment/index.html#introduction +#### https://docs.oracle.com/en/learn/migrate-api-to-api-gateway/#introduction + +def auth_idcs(token, url, clientID, secretID): + url = url + "/oauth2/v1/introspect" + + auth = clientID + ":" + secretID + auth_bytes = auth.encode("ascii") + auth_base64_bytes = base64.b64encode(auth_bytes) + auth_base64_message = auth_base64_bytes.decode("ascii") + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + auth_base64_message + } + + payload = "token=" + token + + response = requests.request("POST", url, headers=headers, data=payload) + return response + +def beautify_str(str_msg): + msg = str(str_msg.encode('unicode_escape').decode("utf-8")).replace("\\n", " ") + split_str = msg.split() + return " ".join(split_str) + +### + +def replace_regex(variavel): + variavel = variavel.replace("\\d", "[0-9]") + variavel = variavel.replace("\\D", "[^0-9]") + variavel = variavel.replace("\\.", "[.]") + variavel = variavel.replace("\\w", "[a-zA-Z0-9_]") + variavel = variavel.replace("\\W", "[^a-zA-Z0-9_]") + variavel = variavel.replace("/^", "^") + variavel = variavel.replace("$/", "$") + + return variavel + +def remove_property(dictionary, property_name): + keys_to_delete = [key for key in dictionary if key == property_name] + for key in keys_to_delete: + if ("\\s" in dictionary[key] or "\\S" in dictionary[key] or "\\w" in dictionary[key] or "\\W" in dictionary[key] + or "\\b" in dictionary[key] or "\\B" in dictionary[key] or "\\A" in dictionary[key] or "\\Z" in dictionary[key]): + del dictionary[key] + else: + dictionary[key] = replace_regex(dictionary[key]) + for value in dictionary.values(): + if isinstance(value, dict): + remove_property(value, property_name) + elif isinstance(value, list): + for item in value: + if isinstance(item, dict): + remove_property(item, property_name) + return dictionary + +def count_attributes(json_data): + count = 0 + for key, value in json_data.items(): + count += 1 + if isinstance(value, dict): + count += count_attributes(value) + return count + +def read_secret_value(secret_client, secret_id): + response = secret_client.get_secret_bundle(secret_id) + base64_Secret_content = response.data.secret_bundle_content.content + base64_secret_bytes = base64_Secret_content.encode('ascii') + base64_message_bytes = base64.b64decode(base64_secret_bytes) + secret_content = base64_message_bytes.decode('ascii') + return secret_content + +def handler(ctx, data: io.BytesIO = None): + config = oci.config.from_file("config") + logging = oci.loggingingestion.LoggingClient(config) + + # functions context variables + app_context = dict(ctx.Config()) + + jsonData = "" + + try: + header = json.loads(data.getvalue().decode('utf-8'))["data"] + + # IDCS Validation + url = "https://idcs-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.identity.oraclecloud.com" + ClientId = read_secret_value(secret_client, "ocid1.vaultsecret.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + ClientSecret = read_secret_value(secret_client, "ocid1.vaultsecret.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + + # JSON Items counter + jsonData = dict(json.loads(data.getvalue().decode('utf-8')).get("data"))["body"] + jsonData = dict(json.loads(jsonData)) + c = count_attributes(jsonData) + if (c > 21): + rdata = json.dumps({ + "active": False, + "context": { + "status_code": 401, + "message": "JSON exception", + "error": "JSON exception", + }}) + + return response.Response( + ctx, + status_code=401, + response_data=rdata + ) + + try: + body = dict(json.loads(data.getvalue().decode('utf-8')).get("data"))["body"] + body = json.loads(body) + except: + body = None + # body content + body_schema_validation = None + try: + if (".apigatewayapi." not in header["body_schema_validation"]): + body_schema_validation = ast.literal_eval(header["body_schema_validation"]) + else: + body_schema_validation = header["body_schema_validation"] + except: + body_schema_validation = None + + # header values + access_token = header["token"] + + authorization = auth_idcs(access_token, url, ClientId, ClientSecret) + try: + if (authorization.json().get("active") != True): + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) + except(Exception) as ex1: + jsonData = 'error parsing json payload: ' + str(ex1) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(a): " + jsonData, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + rdata = json.dumps({ + "active": False, + "context": { + "status_code": 401, + "message": "Unauthorized", + "body": body, + "body_schema_validation": json.dumps(body_schema_validation), + "error": str(ex1) + }}) + + return response.Response( + ctx, + status_code=401, + response_data=rdata + ) + + rdata = json.dumps({ + "active": True, + "context": { + "body": body, + "body_schema_validation": json.dumps(body_schema_validation) + }}) + + # Validate API spec + if (body_schema_validation != None): + if (".apigatewayapi." not in header["body_schema_validation"]): + # Com validacao direto por propriedades (sem schemas e referencias) + try: + validate(body, body_schema_validation["schema"]) + return response.Response( + ctx, response_data=rdata, + status_code=200, + headers={"Content-Type": "application/json", "body": json.dumps(body)} + ) + except(Exception) as ex2: + error_msg = beautify_str(str(ex2)) + redaction = Redaction.Redaction() + error_msg = redaction.redact(sensitive_pattern=SENSITIVE_PATTERNS, attribute_pattern=ATTRIBUTE_PATTERNS, message=error_msg) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(b): " + error_msg, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + rdata = json.dumps({ + "active": False, + "context": { + "status_code": 401, + "message": "Unauthorized", + "body": body, + "body_schema_validation": json.dumps(body_schema_validation), + "error": error_msg + }}) + + return response.Response( + ctx, + status_code=401, + response_data=rdata + ) + else: + # Com schema de validação - Tanto swagger como Open API 3 + try: + bravado_config = { + 'validate_swagger_spec': False, + 'validate_requests': False, + 'validate_responses': False, + 'use_models': True, + } + contents = body_schema_validation.split(",") + apigateway_client = oci.apigateway.ApiGatewayClient(config) + api_spec = apigateway_client.get_api_content(contents[1]) + spec_dict = json.loads(api_spec.data.content) + spec_dict = remove_property(spec_dict, "pattern") + + spec = Spec.from_dict(spec_dict, config=bravado_config) + try: + schema = spec_dict["definitions"][contents[0]] + except: + schema = spec_dict["components"]["schemas"][contents[0]] + + schema_without_pattern = remove_property(schema, "pattern") + + validate_object(spec, schema_without_pattern, body) + except (Exception) as ex3: + error_msg = beautify_str(str(ex3)) + redaction = Redaction.Redaction() + error_msg = redaction.redact(sensitive_pattern=SENSITIVE_PATTERNS, attribute_pattern=ATTRIBUTE_PATTERNS, message=error_msg) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(b): " + error_msg, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + rdata = json.dumps({ + "active": False, + "context": { + "status_code": 401, + "message": "Unauthorized", + "body": body, + "body_schema_validation": json.dumps(body_schema_validation), + "error": error_msg + }}) + + return response.Response( + ctx, + status_code=401, + response_data=rdata + ) + + return response.Response( + ctx, response_data=rdata, + status_code=200, + headers={"Content-Type": "application/json", "body_schema_validation": body_schema_validation, "body": json.dumps(body)} + ) + + except(Exception) as ex: + jsonData = 'error parsing json payload: ' + str(ex) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(c): " + jsonData, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + pass + + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) diff --git a/files/authApi/config b/files/authApi/config new file mode 100644 index 0000000..74fb1cd --- /dev/null +++ b/files/authApi/config @@ -0,0 +1,7 @@ +[DEFAULT] +user=ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +fingerprint=36:04:63:cd:36:04:63:cd:36:04:63:cd:36:04:63:cd +key_file=oci_api_key.pem +tenancy=ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +region=us-ashburn-1 + diff --git a/files/authApi/config.json b/files/authApi/config.json new file mode 100644 index 0000000..cfdb527 --- /dev/null +++ b/files/authApi/config.json @@ -0,0 +1,12 @@ +{ + "ClientId" : "0b8cd92bb60b8cd92bb60b8cd92bb6", + "ClientSecret" : "41964196-2cfb-2cfb-2cfb-63246a63246a", + "BaseUrl" : "https://idcs-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.identity.oraclecloud.com", + "AudienceServiceUrl" : "https://idcs-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.identity.oraclecloud.com", + "scope" : "aaaaaaaaaaaaaaaaaaaaaaa.apigateway.us-ashburn-1.oci.customer-oci.com/super_scope", + "TokenIssuer" : "https://identity.oraclecloud.com", + "redirectURL": "http://localhost:8000/callback", + "logoutSufix":"/oauth2/v1/userlogout", + "LogLevel":"INFO", + "ConsoleLog":"True" +} \ No newline at end of file diff --git a/files/authApi/func.py b/files/authApi/func.py new file mode 100644 index 0000000..cd44534 --- /dev/null +++ b/files/authApi/func.py @@ -0,0 +1,340 @@ +import base64 +import json +import io +from fdk import response +import oci +import requests +import time +from openapi_schema_validator import validate +import os +import ast +from bravado_core.spec import Spec +from bravado_core.validate import validate_object +from datetime import datetime +from random import randrange + +import Redaction + +SENSITIVE_PATTERNS = [ + r"\d{3}-\d{2}-\d{4}", # Social Security Number (SSN) pattern + r"\d{4}[-\s]\d{4}[-\s]\d{4}[-\s]\d{4}", # Credit card number pattern + r"\(?\d{3}\)?[-\s.]?\d{3}[-\s.]?\d{4}", # Phone number + r"(0[1-9]|1[0-2])[-/.](0[1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d", # date of birth + r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)", # IP address + r"[a-zA-Z0-9]{32}", # API key + r"^(\d{5}.\d{2}-\d)|(\d{8})$" +] + +ATTRIBUTE_PATTERNS = [ + "documentNumber", + "documentCustodyAgentAccountCode", + "isinCode", + "payingAgentAccountCode", + "registrationParticipantAccountCode", + "nome", + "$ref", + "cpf", + "teste", + "valor", + "original", + "type", + "solicitacaoPagador", + "expiracao", + "chave", + "description", + "items", + "required", + "x-scope", + "maxLength" +] + +#### IDCS Routines +#### https://docs.oracle.com/en/learn/apigw-modeldeployment/index.html#introduction +#### https://docs.oracle.com/en/learn/migrate-api-to-api-gateway/#introduction + +def auth_idcs(token, url, clientID, secretID): + url = url + "/oauth2/v1/introspect" + + auth = clientID + ":" + secretID + auth_bytes = auth.encode("ascii") + auth_base64_bytes = base64.b64encode(auth_bytes) + auth_base64_message = auth_base64_bytes.decode("ascii") + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + auth_base64_message + } + + payload = "token=" + token + + response = requests.request("POST", url, headers=headers, data=payload) + return response + +def beautify_str(str_msg): + msg = str(str_msg.encode('unicode_escape').decode("utf-8")).replace("\\n", " ") + split_str = msg.split() + return " ".join(split_str) + +### + +def replace_regex(variavel): + variavel = variavel.replace("\\d", "[0-9]") + variavel = variavel.replace("\\D", "[^0-9]") + variavel = variavel.replace("\\.", "[.]") + variavel = variavel.replace("\\w", "[a-zA-Z0-9_]") + variavel = variavel.replace("\\W", "[^a-zA-Z0-9_]") + variavel = variavel.replace("/^", "^") + variavel = variavel.replace("$/", "$") + + return variavel + +def remove_property(dictionary, property_name): + keys_to_delete = [key for key in dictionary if key == property_name] + for key in keys_to_delete: + if ("\\s" in dictionary[key] or "\\S" in dictionary[key] or "\\w" in dictionary[key] or "\\W" in dictionary[key] + or "\\b" in dictionary[key] or "\\B" in dictionary[key] or "\\A" in dictionary[key] or "\\Z" in dictionary[key]): + del dictionary[key] + else: + dictionary[key] = replace_regex(dictionary[key]) + for value in dictionary.values(): + if isinstance(value, dict): + remove_property(value, property_name) + elif isinstance(value, list): + for item in value: + if isinstance(item, dict): + remove_property(item, property_name) + return dictionary + +def count_attributes(json_data): + count = 0 + for key, value in json_data.items(): + count += 1 + if isinstance(value, dict): + count += count_attributes(value) + return count + +def handler(ctx, data: io.BytesIO = None): + config = oci.config.from_file("config") + logging = oci.loggingingestion.LoggingClient(config) + + # functions context variables + app_context = dict(ctx.Config()) + + jsonData = "" + + try: + header = json.loads(data.getvalue().decode('utf-8'))["data"] + + # IDCS Validation + url = "https://idcs-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.identity.oraclecloud.com" + ClientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ClientSecret = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + + # JSON Items counter + jsonData = dict(json.loads(data.getvalue().decode('utf-8')).get("data"))["body"] + jsonData = dict(json.loads(jsonData)) + c = count_attributes(jsonData) + if (c > 21): + rdata = json.dumps({ + "active": False, + "context": { + "status_code": 401, + "message": "JSON exception", + "error": "JSON exception", + }}) + + return response.Response( + ctx, + status_code=401, + response_data=rdata + ) + + try: + body = dict(json.loads(data.getvalue().decode('utf-8')).get("data"))["body"] + body = json.loads(body) + except: + body = None + # body content + body_schema_validation = None + try: + if (".apigatewayapi." not in header["body_schema_validation"]): + body_schema_validation = ast.literal_eval(header["body_schema_validation"]) + else: + body_schema_validation = header["body_schema_validation"] + except: + body_schema_validation = None + + # header values + access_token = header["token"] + + authorization = auth_idcs(access_token, url, ClientId, ClientSecret) + try: + if (authorization.json().get("active") != True): + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) + except(Exception) as ex1: + jsonData = 'error parsing json payload: ' + str(ex1) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(a): " + jsonData, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + rdata = json.dumps({ + "active": False, + "context": { + "status_code": 401, + "message": "Unauthorized", + "body": body, + "body_schema_validation": json.dumps(body_schema_validation), + "error": str(ex1) + }}) + + return response.Response( + ctx, + status_code=401, + response_data=rdata + ) + + rdata = json.dumps({ + "active": True, + "context": { + "body": body, + "body_schema_validation": json.dumps(body_schema_validation) + }}) + + # Validate API spec + if (body_schema_validation != None): + if (".apigatewayapi." not in header["body_schema_validation"]): + # Com validacao direto por propriedades (sem schemas e referencias) + try: + validate(body, body_schema_validation["schema"]) + return response.Response( + ctx, response_data=rdata, + status_code=200, + headers={"Content-Type": "application/json", "body": json.dumps(body)} + ) + except(Exception) as ex2: + error_msg = beautify_str(str(ex2)) + redaction = Redaction.Redaction() + error_msg = redaction.redact(sensitive_pattern=SENSITIVE_PATTERNS, attribute_pattern=ATTRIBUTE_PATTERNS, message=error_msg) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(b): " + error_msg, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + rdata = json.dumps({ + "active": False, + "context": { + "status_code": 401, + "message": "Unauthorized", + "body": body, + "body_schema_validation": json.dumps(body_schema_validation), + "error": error_msg + }}) + + return response.Response( + ctx, + status_code=401, + response_data=rdata + ) + else: + # Com schema de validação - Tanto swagger como Open API 3 + try: + bravado_config = { + 'validate_swagger_spec': False, + 'validate_requests': False, + 'validate_responses': False, + 'use_models': True, + } + contents = body_schema_validation.split(",") + apigateway_client = oci.apigateway.ApiGatewayClient(config) + api_spec = apigateway_client.get_api_content(contents[1]) + spec_dict = json.loads(api_spec.data.content) + spec_dict = remove_property(spec_dict, "pattern") + + spec = Spec.from_dict(spec_dict, config=bravado_config) + try: + schema = spec_dict["definitions"][contents[0]] + except: + schema = spec_dict["components"]["schemas"][contents[0]] + + schema_without_pattern = remove_property(schema, "pattern") + + validate_object(spec, schema_without_pattern, body) + except (Exception) as ex3: + error_msg = beautify_str(str(ex3)) + redaction = Redaction.Redaction() + error_msg = redaction.redact(sensitive_pattern=SENSITIVE_PATTERNS, attribute_pattern=ATTRIBUTE_PATTERNS, message=error_msg) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(b): " + error_msg, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + rdata = json.dumps({ + "active": False, + "context": { + "status_code": 401, + "message": "Unauthorized", + "body": body, + "body_schema_validation": json.dumps(body_schema_validation), + "error": error_msg + }}) + + return response.Response( + ctx, + status_code=401, + response_data=rdata + ) + + return response.Response( + ctx, response_data=rdata, + status_code=200, + headers={"Content-Type": "application/json", "body_schema_validation": body_schema_validation, "body": json.dumps(body)} + ) + + except(Exception) as ex: + jsonData = 'error parsing json payload: ' + str(ex) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error(c): " + jsonData, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + pass + + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) diff --git a/files/authApi/func.yaml b/files/authApi/func.yaml new file mode 100644 index 0000000..93dd3b7 --- /dev/null +++ b/files/authApi/func.yaml @@ -0,0 +1,8 @@ +schema_version: 20180708 +name: auth-api +version: 0.0.523 +runtime: python +build_image: fnproject/python:3.9-dev +run_image: fnproject/python:3.9 +entrypoint: /python/bin/fdk /function/func.py handler +memory: 256 diff --git a/files/authApi/oci_api_key.pem b/files/authApi/oci_api_key.pem new file mode 100644 index 0000000..2dba649 --- /dev/null +++ b/files/authApi/oci_api_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK ++PlyICemh7G2GNTwleCu61CVYaVcXxZG8LQkHAHCykuq+R7d6lwxkHQTIyKmUj+o +6BCWIQKBgQCAHaQZ4p/XDHVqajity2YCauQKM7eD0cyUnY9h+MKkih1g7brU43DR +u1yJoOnQzddapVr7yVXMl874mU+Jgm7arh+XRL8WuV2RtltKurBhYqtSwiGg0JFx +pRZm1D73NtXRaTSSwYdXakQjPb4FaFdwBouxVylP6GSy4kI2iva3og== +-----END RSA PRIVATE KEY----- diff --git a/files/authApi/requirements.txt b/files/authApi/requirements.txt new file mode 100644 index 0000000..4b612e3 --- /dev/null +++ b/files/authApi/requirements.txt @@ -0,0 +1,10 @@ +fdk>=0.1.54 +requests +oci +cryptography +six +PyJWT +py3_lru_cache +simplejson +openapi-schema-validator +bravado-core diff --git a/files/createapi/config b/files/createapi/config new file mode 100644 index 0000000..74fb1cd --- /dev/null +++ b/files/createapi/config @@ -0,0 +1,7 @@ +[DEFAULT] +user=ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +fingerprint=36:04:63:cd:36:04:63:cd:36:04:63:cd:36:04:63:cd +key_file=oci_api_key.pem +tenancy=ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +region=us-ashburn-1 + diff --git a/files/createapi/config.json b/files/createapi/config.json new file mode 100644 index 0000000..cfdb527 --- /dev/null +++ b/files/createapi/config.json @@ -0,0 +1,12 @@ +{ + "ClientId" : "0b8cd92bb60b8cd92bb60b8cd92bb6", + "ClientSecret" : "41964196-2cfb-2cfb-2cfb-63246a63246a", + "BaseUrl" : "https://idcs-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.identity.oraclecloud.com", + "AudienceServiceUrl" : "https://idcs-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.identity.oraclecloud.com", + "scope" : "aaaaaaaaaaaaaaaaaaaaaaa.apigateway.us-ashburn-1.oci.customer-oci.com/super_scope", + "TokenIssuer" : "https://identity.oraclecloud.com", + "redirectURL": "http://localhost:8000/callback", + "logoutSufix":"/oauth2/v1/userlogout", + "LogLevel":"INFO", + "ConsoleLog":"True" +} \ No newline at end of file diff --git a/files/createapi/createApi.json b/files/createapi/createApi.json new file mode 100644 index 0000000..8c49137 --- /dev/null +++ b/files/createapi/createApi.json @@ -0,0 +1,136 @@ +{ + "compartment_id": "", + "defined_tags": { + "Oracle-Tags": { + "CreatedBy": "oracleidentitycloudservice/cristiano.hoshikawa@oracle.com", + "CreatedOn": "2024-01-01T13:15:34.193Z" + } + }, + "display_name": "createApi", + "endpoint": "https://aaaaaaaaaaaaaaaaaaaaaaaaaa.apigateway.us-ashburn-1.oci.customer-oci.com/createApi", + "freeform_tags": {}, + "gateway_id": "ocid1.apigateway.oc1.iad.amaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaanamaaaaaan", + "id": "ocid1.apideployment.oc1.iad.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "lifecycle_details": null, + "lifecycle_state": "ACTIVE", + "path_prefix": "/createApi", + "specification": { + "logging_policies": { + "access_log": null, + "execution_log": { + "is_enabled": null, + "log_level": "INFO" + } + }, + "request_policies": { + "authentication": { + "cache_key": [ + "token", + "displayName", + "apiCompartmentId" + ], + "function_id": "", + "is_anonymous_access_allowed": false, + "parameters": { + "apiCompartmentId": "request.headers[apiCompartmentId]", + "body": "request.body", + "displayName": "request.headers[displayName]", + "token": "request.headers[token]" + }, + "token_header": null, + "token_query_param": null, + "type": "CUSTOM_AUTHENTICATION", + "validation_failure_policy": null + }, + "cors": null, + "dynamic_authentication": null, + "mutual_tls": { + "allowed_sans": [], + "is_verified_certificate_required": false + }, + "rate_limiting": null, + "usage_plans": null + }, + "routes": [ + { + "backend": { + "body": "{\"status\": \"success\"}", + "headers": [], + "status": 200, + "type": "STOCK_RESPONSE_BACKEND" + }, + "logging_policies": { + "access_log": null, + "execution_log": { + "is_enabled": null, + "log_level": null + } + }, + "methods": [ + "POST" + ], + "path": "/create", + "request_policies": { + "authorization": { + "type": "AUTHENTICATION_ONLY" + }, + "body_validation": null, + "cors": null, + "header_transformations": { + "filter_headers": null, + "rename_headers": null, + "set_headers": { + "items": [ + { + "if_exists": "OVERWRITE", + "name": "token", + "values": [ + "${request.headers[token]}" + ] + }, + { + "if_exists": "OVERWRITE", + "name": "displayName", + "values": [ + "${request.headers[displayName]}" + ] + }, + { + "if_exists": "OVERWRITE", + "name": "apiCompartmentId", + "values": [ + "${request.headers[apiCompartmentId]}" + ] + } + ] + } + }, + "header_validations": null, + "query_parameter_transformations": null, + "query_parameter_validations": null, + "response_cache_lookup": null + }, + "response_policies": { + "header_transformations": { + "filter_headers": null, + "rename_headers": null, + "set_headers": { + "items": [ + { + "if_exists": "APPEND", + "name": "api_id", + "values": [ + "${request.auth[api_id]}" + ] + } + ] + } + }, + "response_cache_store": null + } + } + ] + }, + "time_created": "2024-01-01T13:15:35.448000+00:00", + "time_updated": "2024-01-01T13:39:00.396000+00:00" +} diff --git a/files/createapi/func.py b/files/createapi/func.py new file mode 100644 index 0000000..99c7f9e --- /dev/null +++ b/files/createapi/func.py @@ -0,0 +1,261 @@ +import base64 +import json +import io +from fdk import response +import oci +import requests +import yaml +import datetime + +#### IDCS Routines +#### https://docs.oracle.com/en/learn/apigw-modeldeployment/index.html#introduction +#### https://docs.oracle.com/en/learn/migrate-api-to-api-gateway/#introduction + +def auth_idcs(token, url, clientID, secretID): + url = url + "/oauth2/v1/introspect" + + auth = clientID + ":" + secretID + auth_bytes = auth.encode("ascii") + auth_base64_bytes = base64.b64encode(auth_bytes) + auth_base64_message = auth_base64_bytes.decode("ascii") + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': 'Basic ' + auth_base64_message + } + + payload = "token=" + token + + response = requests.request("POST", url, headers=headers, data=payload) + return response + +#Function used to load the configurations from the config.json file +def getOptions(): + fo = open("config.json", "r") + config = fo.read() + options = json.loads(config) + return options + +### OCI API Gateway Migration Routines + +def migrate_to_apigw(payload, url, clientID, secretID): + auth = clientID + ":" + secretID + auth_bytes = auth.encode("ascii") + auth_base64_bytes = base64.b64encode(auth_bytes) + auth_base64_message = auth_base64_bytes.decode("ascii") + + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Basic ' + auth_base64_message + } + + response = requests.request("POST", url, headers=headers, data=payload) + return response + +def find_base_path(strPath): + base_path = strPath.split('/')[1] + if (len(base_path) == 0): + base_path = strPath + else: + base_path = "/" + base_path + return base_path + +def find_path(strPath): + base_path = strPath.split('/') + if (len(base_path) == 0): + return strPath + else: + auxPath = "" + skipCount = 0 + for b in base_path: + if (skipCount > 1): + auxPath = auxPath + "/" + b + skipCount = skipCount + 1 + base_path = auxPath + return auxPath + +def process_api_spec(displayName, compartmentId, environment, swagger): + type = "REST" + config = oci.config.from_file("config") + apigateway_client = oci.apigateway.ApiGatewayClient(config) + logging = oci.loggingingestion.LoggingClient(config) + #----------------------------------------------------------------- + try: + data = swagger + fullSpec = json.loads(data) + + version = "3" + try: + version = (fullSpec["swagger"])[:1] + except: + version = (fullSpec["openapi"])[:1] + + print("version", version) + + if (version == "3"): + endPoint = fullSpec["servers"][0]["url"] + else: + endPoint = fullSpec["host"] + + listApis = apigateway_client.list_apis(compartment_id=compartmentId, display_name=displayName, lifecycle_state="ACTIVE") + apis = json.loads(str(listApis.data)) + c = len(apis["items"]) + api_id = "" + + if (c == 0): + print("create api") + create_api_response = apigateway_client.create_api( + create_api_details=oci.apigateway.models.CreateApiDetails( + compartment_id=compartmentId, + display_name=displayName, + content=data)) + api_created = json.loads(str(create_api_response.data)) + api_id = api_created + else: + print("update api") + update_api_response = apigateway_client.update_api(api_id=apis["items"][0]["id"], + update_api_details=oci.apigateway.models.UpdateApiDetails( + display_name=displayName, + content=data)) + api_updated = dict(update_api_response.headers) + api_id = api_updated + + return api_id + + except(Exception) as ex: + raise + +def DateEncoder(obj): + if isinstance(obj, datetime.datetime): + return obj.strftime('%Y-%m-%d') + +def is_json(swagger): + try: + body = json.loads(swagger) + return True + except: + try: + yaml_object = yaml.safe_load(swagger) # yaml_object will be a list or a dict + s = json.dumps(yaml_object, indent=2, default=DateEncoder) + return False + except: + return False + +def convert_json(swagger): + yaml_object = yaml.safe_load(swagger) # yaml_object will be a list or a dict + return json.dumps(yaml_object, indent=2, default=DateEncoder) + +### + +def handler(ctx, data: io.BytesIO = None): + config = oci.config.from_file("config") + logging = oci.loggingingestion.LoggingClient(config) + + # functions context variables + app_context = dict(ctx.Config()) + + jsonData = "" + + options = getOptions() + + try: + header = json.loads(data.getvalue().decode('utf-8'))["data"] + url = options["BaseUrl"] + body = dict(json.loads(data.getvalue().decode('utf-8')).get("data"))["body"] + # body content + swagger = str(body) + if (is_json(swagger)): + body = json.loads(body) + else: + body = json.loads(convert_json(swagger)) + swagger = convert_json(swagger) + + environment = "DEV" + + # header values + access_token = header["token"] + displayName = header["displayName"] + compartmentId = header['apiCompartmentId'] + + authorization = auth_idcs(access_token, url, options["ClientId"], options["ClientSecret"]) + try: + if (authorization.json().get("active") != True): + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) + except(Exception) as ex1: + jsonData = 'error parsing json payload: ' + str(ex1) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error: " + jsonData, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) + + # Create API spec + api_id = process_api_spec(displayName, compartmentId, environment, swagger) + + rdata = json.dumps({ + "active": True, + "context": { + "environment": environment, + "display_name": displayName, + "api_id": json.dumps(api_id) + }}) + + # put_logs_response = logging.put_logs( + # log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + # put_logs_details=oci.loggingingestion.models.PutLogsDetails( + # specversion="EXAMPLE-specversion-Value", + # log_entry_batches=[ + # oci.loggingingestion.models.LogEntryBatch( + # entries=[ + # oci.loggingingestion.models.LogEntry( + # data="request payload: " + json.dumps(header), + # id="ocid1.test.oc1..00000001.EXAMPLE-id-Value-1")], + # source="EXAMPLE-source-Value", + # type="EXAMPLE-type-Value")])) + + + return response.Response( + ctx, response_data=rdata, + status_code=200, + headers={"Content-Type": "application/json", "data": rdata} + ) + + except(Exception) as ex: + jsonData = 'error parsing json payload: ' + str(ex) + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="error: " + jsonData + "/" + swagger, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + + pass + + return response.Response( + ctx, + status_code=401, + response_data=json.dumps({"active": False, "wwwAuthenticate": jsonData}) + ) \ No newline at end of file diff --git a/files/createapi/func.yaml b/files/createapi/func.yaml new file mode 100644 index 0000000..a184a14 --- /dev/null +++ b/files/createapi/func.yaml @@ -0,0 +1,8 @@ +schema_version: 20180708 +name: create-api +version: 0.0.37 +runtime: python +build_image: fnproject/python:3.9-dev +run_image: fnproject/python:3.9 +entrypoint: /python/bin/fdk /function/func.py handler +memory: 256 diff --git a/files/createapi/oci_api_key.pem b/files/createapi/oci_api_key.pem new file mode 100644 index 0000000..2dba649 --- /dev/null +++ b/files/createapi/oci_api_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK ++PlyICemh7G2GNTwleCu61CVYaVcXxZG8LQkHAHCykuq+R7d6lwxkHQTIyKmUj+o +6BCWIQKBgQCAHaQZ4p/XDHVqajity2YCauQKM7eD0cyUnY9h+MKkih1g7brU43DR +u1yJoOnQzddapVr7yVXMl874mU+Jgm7arh+XRL8WuV2RtltKurBhYqtSwiGg0JFx +pRZm1D73NtXRaTSSwYdXakQjPb4FaFdwBouxVylP6GSy4kI2iva3og== +-----END RSA PRIVATE KEY----- diff --git a/files/createapi/requirements.txt b/files/createapi/requirements.txt new file mode 100644 index 0000000..500c823 --- /dev/null +++ b/files/createapi/requirements.txt @@ -0,0 +1,9 @@ +fdk>=0.1.54 +requests +oci +cryptography +six +PyJWT +py3_lru_cache +simplejson +PyYAML diff --git a/files/test/config b/files/test/config new file mode 100644 index 0000000..74fb1cd --- /dev/null +++ b/files/test/config @@ -0,0 +1,7 @@ +[DEFAULT] +user=ocid1.user.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +fingerprint=36:04:63:cd:36:04:63:cd:36:04:63:cd:36:04:63:cd +key_file=oci_api_key.pem +tenancy=ocid1.tenancy.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +region=us-ashburn-1 + diff --git a/files/test/config.json b/files/test/config.json new file mode 100644 index 0000000..cfdb527 --- /dev/null +++ b/files/test/config.json @@ -0,0 +1,12 @@ +{ + "ClientId" : "0b8cd92bb60b8cd92bb60b8cd92bb6", + "ClientSecret" : "41964196-2cfb-2cfb-2cfb-63246a63246a", + "BaseUrl" : "https://idcs-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.identity.oraclecloud.com", + "AudienceServiceUrl" : "https://idcs-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.identity.oraclecloud.com", + "scope" : "aaaaaaaaaaaaaaaaaaaaaaa.apigateway.us-ashburn-1.oci.customer-oci.com/super_scope", + "TokenIssuer" : "https://identity.oraclecloud.com", + "redirectURL": "http://localhost:8000/callback", + "logoutSufix":"/oauth2/v1/userlogout", + "LogLevel":"INFO", + "ConsoleLog":"True" +} \ No newline at end of file diff --git a/files/test/oci_api_key.pem b/files/test/oci_api_key.pem new file mode 100644 index 0000000..2dba649 --- /dev/null +++ b/files/test/oci_api_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK +MIIEpAIBAAKCAQEA16UCid4+eyt6kzo+u1BRV4UM8QKfViBZasZBclCOvt8j+lDK ++PlyICemh7G2GNTwleCu61CVYaVcXxZG8LQkHAHCykuq+R7d6lwxkHQTIyKmUj+o +6BCWIQKBgQCAHaQZ4p/XDHVqajity2YCauQKM7eD0cyUnY9h+MKkih1g7brU43DR +u1yJoOnQzddapVr7yVXMl874mU+Jgm7arh+XRL8WuV2RtltKurBhYqtSwiGg0JFx +pRZm1D73NtXRaTSSwYdXakQjPb4FaFdwBouxVylP6GSy4kI2iva3og== +-----END RSA PRIVATE KEY----- diff --git a/files/test/process_api_spec.py b/files/test/process_api_spec.py new file mode 100644 index 0000000..887d212 --- /dev/null +++ b/files/test/process_api_spec.py @@ -0,0 +1,681 @@ +import base64 +import json +import io +from fdk import response +import oci +import requests +import time +from itertools import groupby +import yaml +import datetime + +# DEFINIR AS VARIAVEIS +# +# Método: process_api_spec() +# displayName = "qrcode" +# compartmentId = "ocid1.compartment.oc1..aaaaaaaaqomaaaaaaaaqomaaaaaaaaqomaaaaaaaaqomaaaaaaaaqomaaaaaaaaqom" +# config = oci.config.from_file(profile_name='DEFAULT') +# +# Método: getSpec() +# text_file = open("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-15/qrcode.yaml", "r") + +def migrate_to_apigw(payload, url, clientID, secretID): + auth = clientID + ":" + secretID + auth_bytes = auth.encode("ascii") + auth_base64_bytes = base64.b64encode(auth_bytes) + auth_base64_message = auth_base64_bytes.decode("ascii") + + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Basic ' + auth_base64_message + } + + response = requests.request("POST", url, headers=headers, data=payload) + return response + + +def getSpec(name): + text_file = open(name, "r", encoding='utf-8') + data = text_file.read() + text_file.close() + + if (is_json(data)): + data = data + else: + data = convert_json(data) + + return data + + +def find_base_path(strPath): + base_path = strPath.split('/')[1] + if (len(base_path) == 0): + base_path = strPath + else: + base_path = "/" + base_path + return base_path + +def has_path_endpoint(endPoint): + endPointAux = endPoint.replace("//", "#") + endPointSplited = endPointAux.split('/') + if (len(endPointSplited) > 1): + return True + else: + return False + +def concatSplited(endPointSplited): + count = 0 + endPointStr = "" + for item in endPointSplited: + if (count > 0): + endPointStr = endPointStr + "/" + item + count = count + 1 + return endPointStr + +def find_base_pathendpoint(endPoint, strPath): + base_path = strPath.split('/')[1] + if (len(base_path) == 0 and has_path_endpoint(endPoint)): + endPointAux = endPoint.replace("//", "#") + endPointSplited = endPointAux.split('/') + if (len(endPointSplited) > 1): + endPointSplitedStr = concatSplited(endPointSplited) + if (endPointSplitedStr != None): + base_path = endPointSplitedStr + else: + base_path = strPath + else: + base_path = strPath + else: + endPointAux = endPoint.replace("//", "#") + endPointSplited = endPointAux.split('/') + if (len(endPointSplited) > 1): + endPointSplitedStr = concatSplited(endPointSplited) + if (endPointSplitedStr != None): + base_path = endPointSplitedStr + endPoint = endPointSplited[0].replace("#", "//") + else: + base_path = "/" + base_path + else: + base_path = "/" + base_path + return base_path + +def find_base_endpoint(endPoint): + endPointAux = endPoint.replace("//", "#") + endPointSplited = endPointAux.split('/') + if (len(endPointSplited) > 1): + endPointSplitedStr = endPointSplited[1] + if (endPointSplitedStr != None): + endPoint = endPointSplited[0].replace("#", "//") + return endPoint + +def find_path(strPath): + base_path = strPath.split('/') + if (len(base_path) == 0): + return strPath + else: + auxPath = "" + skipCount = 0 + for b in base_path: + if (skipCount > 1): + auxPath = auxPath + "/" + b + skipCount = skipCount + 1 + base_path = auxPath + return auxPath + +def removeLastSlash(path): + return path.rstrip("/") + +def accMethods(routes, path, status): + METHOD = "" + for spec in routes: + if (find_path(spec["path"]) == path and spec["backend"]["status"] == status): + for method in spec["methods"]: + if (method not in METHOD): + METHOD = (METHOD + " " + method).lstrip().upper() + return METHOD + +def accMethods_v2(routes, path, status): + METHOD = "" + for spec in routes: + if (spec["path"] == path and spec["backend"]["status"] == status): + for method in spec["methods"]: + if (method not in METHOD): + METHOD = (METHOD + " " + method).lstrip().upper() + return METHOD + +def accMethods_v3(routes, path, status): + METHOD = "" + for spec in routes: + if (spec["path"] == path and spec["backend"]["status"] == status): + for method in spec["methods"]: + if (method not in METHOD): + METHOD = (METHOD + " " + method).lstrip().upper() + return METHOD + +def check_endpoint(schemes, endpoint): + if (schemes == ""): + if (endpoint.find("http://") == -1 and endpoint.find("https://") == -1): + endpoint = "https://" + endpoint + else: + if (endpoint.find("http://") == -1 and endpoint.find("https://") == -1): + if (schemes.find("://") == -1): + endpoint = schemes + "://" + endpoint + else: + endpoint = schemes + endpoint + return endpoint + +def key_func(k): + return k['PATH'] + +def group_by(payload): + payload = json.loads(payload) + INFO = sorted(payload, key=key_func) + result_payload = [ ] + for key, value in groupby(INFO, key_func): + list_elements = [ ] + method_list = "" + for element in list(value): + list_elements.append(element) + for subItem in list_elements: + item = json.loads(json.dumps(subItem)) + if (item["METHOD"] not in method_list): + method_list = (method_list + " " + item["METHOD"]).lstrip().upper() + API_NAME = item["API_NAME"] + TYPE = item["TYPE"] + ENVIRONMENT = item["ENVIRONMENT"] + PATH_PREFIX = item["PATH_PREFIX"] + PATH = item["PATH"] + ENDPOINT = item["ENDPOINT"] + SCHEMA_BODY_VALIDATION = item["SCHEMA_BODY_VALIDATION"] + result_payload.append({"API_NAME": API_NAME, "TYPE": TYPE, "ENVIRONMENT": ENVIRONMENT, "PATH_PREFIX": PATH_PREFIX, "PATH": PATH, "ENDPOINT": ENDPOINT, "METHOD": method_list, "SCHEMA_BODY_VALIDATION": SCHEMA_BODY_VALIDATION}) + return result_payload + +def verify_path(json_data_list): + list_final = [] + for item in json_data_list: + if (item["PATH"] == ""): + for item2 in json_data_list: + if (item2["PATH"] == ""): + list_final.append({ + 'API_NAME': item2["API_NAME"], + 'TYPE': item2["TYPE"], + 'ENVIRONMENT': item2["ENVIRONMENT"], + 'METHOD': item2["METHOD"], + 'PATH_PREFIX': "/", + 'PATH': item2["PATH_PREFIX"], + 'ENDPOINT': item2["ENDPOINT"], + 'SCHEMA_BODY_VALIDATION': item2["SCHEMA_BODY_VALIDATION"], + 'CONTENT_TYPE': item2["CONTENT_TYPE"] + }) + else: + list_final.append({ + 'API_NAME': item2["API_NAME"], + 'TYPE': item2["TYPE"], + 'ENVIRONMENT': item2["ENVIRONMENT"], + 'METHOD': item2["METHOD"], + 'PATH_PREFIX': item2["PATH_PREFIX"], + 'PATH': item2["PATH"], + 'ENDPOINT': item2["ENDPOINT"], + 'SCHEMA_BODY_VALIDATION': item2["SCHEMA_BODY_VALIDATION"], + 'CONTENT_TYPE': item2["CONTENT_TYPE"] + }) + + return list_final + return json_data_list + +def DateEncoder(obj): + if isinstance(obj, datetime.datetime): + return obj.strftime('%Y-%m-%d') + +def is_json(swagger): + try: + body = json.loads(swagger) + return True + except: + try: + yaml_object = yaml.safe_load(swagger) # yaml_object will be a list or a dict + s = json.dumps(yaml_object, indent=2, default=DateEncoder) + return False + except: + return False + +def replace_escape_chars(obj): + for k, v in obj.items(): + if isinstance(v, str): + obj[k] = v.replace('\\\\', '\\"') + elif isinstance(v, dict): + obj[k] = replace_escape_chars(v) + elif isinstance(v, list): + for i in range(len(v)): + if isinstance(v[i], str): + v[i] = v[i].replace('\\\\', '\\"') + elif isinstance(v[i], dict): + v[i] = replace_escape_chars(v[i]) + return obj + +def convert_json(swagger): + yaml_object = yaml.safe_load(swagger) # yaml_object will be a list or a dict + x = json.dumps(yaml_object, ensure_ascii=False, indent=2, default=DateEncoder).encode('utf-8') + return x.decode() + +def process_api_spec(): + # displayName = "EXEMPLO" + compartmentId = "ocid1.compartment.oc1..aaaaaaaaqom2belitvh5ubr342rgzyeycvyg3zt6b4i4owmkzpnpwft37rga" + environment = "QA" + type = "REST" + rate_limit = "2500,CLIENT_IP" + + try: + rate_config = rate_limit.split(',') + rate_seconds = int(rate_config[0]) + rate_key = rate_config[1] + print(rate_seconds) + print(rate_key) + except: + print("erro") + + + # Create a default config using DEFAULT profile in default location + # Refer to + # https://docs.cloud.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm#SDK_and_CLI_Configuration_File + # for more info + config = oci.config.from_file(profile_name='DEFAULT') + + # TELEMETRY + # monitoring_client = oci.monitoring.MonitoringClient(config, service_endpoint="https://telemetry-ingestion.us-ashburn-1.oraclecloud.com/20180401") + # + # post_metric_data_response = monitoring_client.post_metric_data( + # post_metric_data_details=oci.monitoring.models.PostMetricDataDetails( + # metric_data=[ + # oci.monitoring.models.MetricDataDetails( + # namespace="api_customers", + # compartment_id=compartmentId, + # name="customer_request", + # dimensions={ + # "customer": "Cliente A"}, + # datapoints=[ + # oci.monitoring.models.Datapoint( + # timestamp=datetime.strptime( + # datetime.utcnow().isoformat() + 'Z', + # "%Y-%m-%dT%H:%M:%S.%fZ"), + # value=1, + # count=1)], + # resource_group="API_group", + # metadata={ + # "metadados": "api"})])) + + # Initialize service client with default config file + apigateway_client = oci.apigateway.ApiGatewayClient(config) + + # ----------------------------------------------------------------- + + arquivo = [] + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Testes 2024-03-11/1.0.0-rc2_rcc-interop-agenda_modificado.json") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Testes 2024-03-11/caso1.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Testes 2024-03-11/caso2.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-15/caso2024-03-15.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-15/qrcode.yaml") + #arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-18/1.0.0-rc1_cob 1.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-18/1.0.0-rc1_cobv.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-18/1.0.0-rc1_loc.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-18/1.0.0-rc1_lotecobv.yaml") + #arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-18/1.0.0-rc1_pix.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-18/1.0.0-rc1_webhook.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-20/1.0.0-rc8_cprs.json") + #arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-21/1.0.0-rc1_cob 1.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-21/1.0.0-rc2_rcc-interop-agenda.json") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-22/1.0.0-rc8_cprs.json") + #arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-03-25/Banco B3/1.0.0-rc1_cob.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-06-03/1.0.0-rc1_cob.yaml") + arquivo.append("/Users/cristianohoshikawa/Dropbox/ORACLE/B3/API Gateway/Teste 2024-06-19/1.0.0-rc1_cob.yaml") + display = [] + display.append("Interoperabilidades-Agenda") + display.append("caso-1") + display.append("caso-2") + display.append("caso2024-03-15") + display.append("qrcode") + #display.append("cob") + display.append("cobv") + display.append("loc") + display.append("lotecobv") + #display.append("pix") + display.append("webhook") + display.append("cprs") + #display.append("cob1") + display.append("rcc-interop-agenda") + display.append("Rural") + #display.append("cob") + display.append("pix") + display.append("GI - Modulo PIX Cob") + idxArquivo = 0 + + while idxArquivo < len(arquivo): + + print("---------------------------------------------------------") + print(arquivo[idxArquivo]) + print("") + + data = getSpec(arquivo[idxArquivo]) + fullSpec = json.loads(data) + + swagger = str(data) + swagger2 = str(data) + if (is_json(swagger)): + body = json.loads(swagger) + else: + body = json.loads(convert_json(swagger2)) + swagger = convert_json(data) + + swagger = swagger + + displayName = display[idxArquivo] + + version = "3" + try: + version = (fullSpec["swagger"])[:1] + except: + version = (fullSpec["openapi"])[:1] + + print("version", version) + + if (version == "3"): + endPoint = fullSpec["servers"][0]["url"] + else: + endPoint = fullSpec["host"] + print("url") + print(endPoint) + + listApis = apigateway_client.list_apis(compartment_id=compartmentId, display_name=displayName, + lifecycle_state="ACTIVE") + apis = json.loads(str(listApis.data)) + c = len(apis["items"]) + api_id = apis["items"][0]["id"] + print(api_id) + + try: + get_api = apigateway_client.get_api_deployment_specification(api_id=api_id) + + api_spec = json.loads(str(get_api.data)) + print(api_spec["routes"]) + json_data_list = [] + + endPointOrigin = endPoint + for spec in api_spec["routes"]: + status = spec["backend"]["status"] + specPath = spec["path"] + + for method in spec["methods"]: + METHOD = method.lstrip().upper() + + if (version == "3"): + if (has_path_endpoint(endPointOrigin)): + endPoint = find_base_endpoint(endPointOrigin) + specPath = (find_base_pathendpoint(endPointOrigin, specPath)).replace("//", "/") + fullEndpoint = (endPoint + specPath + spec["path"]).replace("{", "${request.path[").replace("}", "]}") + FULL_PATH = specPath + ENDPOINT = fullEndpoint + PATH = spec["path"] + PATH_PREFIX = specPath + #METHOD = accMethods_v3(api_spec["routes"], spec["path"], status) + else: + fullEndpoint = (endPoint + find_base_path(specPath) + find_path(specPath)).replace("{", "${request.path[").replace("}", "]}") + FULL_PATH = specPath + ENDPOINT = fullEndpoint + PATH = find_path(specPath) + PATH_PREFIX = find_base_path(specPath) + #METHOD = accMethods(api_spec["routes"], find_path(spec["path"]), status) + else: + schemes = "" + try: + schemes = fullSpec["schemes"][0] + except: + schemes = "https" + + fullEndpoint = check_endpoint(schemes, (endPoint + removeLastSlash(fullSpec["basePath"]) + spec["path"]).replace("{", "${request.path[").replace("}", "]}")) + FULL_PATH = fullSpec["basePath"] + spec["path"] + ENDPOINT = fullEndpoint + PATH = spec["path"] + PATH_PREFIX = removeLastSlash(fullSpec["basePath"]) + #METHOD = accMethods_v2(api_spec["routes"], PATH, status) + + OPERATIONID = fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["operationId"] + API_NAME = fullSpec["info"]["title"] + if (version == "3"): + try: + SCHEMA_BODY_VALIDATION = str(fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["requestBody"]["content"]["application/json"]) + CONTENT_TYPE = "application/json" + except: + SCHEMA_BODY_VALIDATION = "" + CONTENT_TYPE = "" + else: + SCHEMA_BODY_VALIDATION = "" + CONTENT_TYPE = "" + try: + reference = str(fullSpec["paths"][spec["path"]][str(spec["methods"][0]).lower()]["parameters"][0]["schema"]["$ref"]).replace("#/definitions/", "") + SCHEMA_BODY_VALIDATION = reference + "," + api_id + CONTENT_TYPE = "application/json" + except: + SCHEMA_BODY_VALIDATION = "" + CONTENT_TYPE = "" + TYPE = type + ENVIRONMENT = environment + json_data_list.append({ + 'API_NAME': API_NAME, + 'TYPE': TYPE, + 'ENVIRONMENT': ENVIRONMENT, + 'METHOD': METHOD, + 'PATH_PREFIX': PATH_PREFIX, + 'PATH': PATH, + 'ENDPOINT': ENDPOINT, + 'SCHEMA_BODY_VALIDATION': SCHEMA_BODY_VALIDATION, + 'CONTENT_TYPE': CONTENT_TYPE + }) + print(API_NAME, TYPE, ENVIRONMENT, METHOD, PATH_PREFIX, PATH, ENDPOINT, SCHEMA_BODY_VALIDATION, CONTENT_TYPE) + + json_data_list = verify_path(json_data_list) + payload = json.dumps(json_data_list) + #json_data_list = { each['PATH'] : each for each in json_data_list}.values() + + # if (version == "2"): + # payload = json.loads(json.dumps(group_by(payload))) + # #json_data_list = { each['PATH'] : each for each in payload}.values() + #payload = json.loads(json.dumps(group_by(payload))) + payload = json.loads(json.dumps(json_data_list)) + print(payload) + # migrate_to_apigw(payload, "https://oic-hoshikawa2-idcci5ks1puo-ia.integration.ocp.oraclecloud.com:443/ic/api/integration/v1/flows/rest/MIGRATE_TO_APIGW/1.0/convert", "OIC_SERVICE_USER_BASICAUTH", "e7ae6069-e471-496e-916d-5dc2ae3edac0") + applyAuthApi(compartmentId=compartmentId, displayName=API_NAME, payload=payload, functionId="", host="", api_gateway_id="", rate_limit=rate_limit) + + c = 0 + idxArquivo = idxArquivo + 1 + + except(Exception) as ex: + print(ex) + time.sleep(2) + + +def applyAuthApi(compartmentId, displayName, payload, functionId, host, api_gateway_id, rate_limit): + config = oci.config.from_file(profile_name='DEFAULT') + logging = oci.loggingingestion.LoggingClient(config) + apigateway_client = oci.apigateway.DeploymentClient(config) + listGateway = apigateway_client.list_deployments(compartment_id=compartmentId, display_name=displayName, lifecycle_state="ACTIVE") + gateway = json.loads(str(listGateway.data)) + ind = -1 + c = -1 + if (len(gateway) > 0): + c = 0 + for item in gateway["items"]: + if (item["gateway_id"] == api_gateway_id): + ind = c + break + c = c + 1 + if (gateway["items"] != [] and c > -1 and ind > -1): + gateway_id = gateway["items"][ind]["gateway_id"] + deployment_id = gateway["items"][ind]["id"] + else: + gateway_id = api_gateway_id + deployment_id = 0 + + try: + rate_config = rate_limit.split(',') + rate_seconds = int(rate_config[0]) + rate_key = rate_config[1] + rate_limiting = oci.apigateway.models.RateLimitingPolicy( + rate_in_requests_per_second=rate_seconds, + rate_key=rate_key) + except: + rate_limiting = None + + path_prefix = "/" + routes = [ ] + new_routes = [ ] + for item in payload: + methods = [item["METHOD"]] + path_prefix = item["PATH_PREFIX"] + callback_url = ("https://" + host + item["PATH_PREFIX"] + "validation-callback" + item["PATH"]).replace("{", "${request.path[").replace("}", "]}") + if (item["SCHEMA_BODY_VALIDATION"] != ""): + put_logs_response = logging.put_logs( + log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + put_logs_details=oci.loggingingestion.models.PutLogsDetails( + specversion="EXAMPLE-specversion-Value", + log_entry_batches=[ + oci.loggingingestion.models.LogEntryBatch( + entries=[ + oci.loggingingestion.models.LogEntry( + data="callback_url: " + callback_url, + id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")], + source="EXAMPLE-source-Value", + type="EXAMPLE-type-Value")])) + routes.append( + oci.apigateway.models.ApiSpecificationRoute( + path=item["PATH"], + backend=oci.apigateway.models.HTTPBackend( + type="HTTP_BACKEND", + url=callback_url, + is_ssl_verify_disabled=False), + methods=methods, + request_policies=oci.apigateway.models.ApiSpecificationRouteRequestPolicies( + header_transformations=oci.apigateway.models.HeaderTransformationPolicy( + set_headers=oci.apigateway.models.SetHeaderPolicy( + items=[ + oci.apigateway.models.SetHeaderPolicyItem( + name="body_schema_validation", + values=[item["SCHEMA_BODY_VALIDATION"]], + if_exists="APPEND")]), + ) + ))) + new_routes.append( + oci.apigateway.models.ApiSpecificationRoute( + path=item["PATH"], + backend=oci.apigateway.models.HTTPBackend( + type="HTTP_BACKEND", + url=item["ENDPOINT"], + is_ssl_verify_disabled=False), + methods=methods, + request_policies=oci.apigateway.models.ApiSpecificationRouteRequestPolicies( + header_transformations=oci.apigateway.models.HeaderTransformationPolicy( + set_headers=oci.apigateway.models.SetHeaderPolicy( + items=[ + oci.apigateway.models.SetHeaderPolicyItem( + name="body_schema_validation", + values=[item["SCHEMA_BODY_VALIDATION"]], + if_exists="APPEND")]), + ) + ) + )) + + else: + routes.append( + oci.apigateway.models.ApiSpecificationRoute( + path=item["PATH"], + backend=oci.apigateway.models.HTTPBackend( + type="HTTP_BACKEND", + url=callback_url, + is_ssl_verify_disabled=False), + methods=methods)) + new_routes.append( + oci.apigateway.models.ApiSpecificationRoute( + path=item["PATH"], + backend=oci.apigateway.models.HTTPBackend( + type="HTTP_BACKEND", + url=item["ENDPOINT"], + is_ssl_verify_disabled=False), + methods=methods)) + + + if (new_routes != [ ]): + validation_deployment_details=oci.apigateway.models.UpdateDeploymentDetails( + display_name=displayName + "-validation", + specification=oci.apigateway.models.ApiSpecification( + request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies( + rate_limiting=rate_limiting, + authentication=oci.apigateway.models.CustomAuthenticationPolicy( + type="CUSTOM_AUTHENTICATION", + function_id=functionId, + is_anonymous_access_allowed=False, + parameters={ + 'token': 'request.headers[token]', + 'body': 'request.body', + 'body_schema_validation': 'request.headers[body_schema_validation]', + 'opc-request-id': 'request.headers[opc-request-id]'}, + cache_key=["token", "opc-request-id"], + validation_failure_policy=oci.apigateway.models.ModifyResponseValidationFailurePolicy( + type="MODIFY_RESPONSE", + response_code="401", + response_message="${request.auth[error]}" + ) + )), + routes=new_routes)) + create_deployment_details=oci.apigateway.models.CreateDeploymentDetails( + display_name=displayName + "-validation", + compartment_id=compartmentId, + gateway_id=gateway_id, + path_prefix= path_prefix + "validation-callback", + specification=oci.apigateway.models.ApiSpecification( + request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies( + rate_limiting=rate_limiting, + authentication=oci.apigateway.models.CustomAuthenticationPolicy( + type="CUSTOM_AUTHENTICATION", + function_id=functionId, + is_anonymous_access_allowed=False, + parameters={ + 'token': 'request.headers[token]', + 'body': 'request.body', + 'body_schema_validation': 'request.headers[body_schema_validation]', + 'opc-request-id': 'request.headers[opc-request-id]'}, + cache_key=["token", "opc-request-id"], + validation_failure_policy=oci.apigateway.models.ModifyResponseValidationFailurePolicy( + type="MODIFY_RESPONSE", + response_code="401", + response_message="${request.auth[error]}" + ) + )), + routes=new_routes)) + #creeateOrUpdateDeployment(compartmendId=compartmentId, displayName=displayName + "-validation", validation_deployment_details=validation_deployment_details, create_deployment_details=create_deployment_details, api_gateway_id=api_gateway_id) + + if (routes != [ ]): + # The 1st layer will not authenticate + validation_deployment_details=oci.apigateway.models.UpdateDeploymentDetails( + display_name=displayName, + specification=oci.apigateway.models.ApiSpecification( + request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies( + rate_limiting=rate_limiting), + routes=routes)) + + create_deployment_details=oci.apigateway.models.CreateDeploymentDetails( + display_name=displayName, + compartment_id=compartmentId, + gateway_id=gateway_id, + path_prefix= path_prefix, + specification=oci.apigateway.models.ApiSpecification( + request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies( + rate_limiting=rate_limiting), + routes=routes)) + + #creeateOrUpdateDeployment(compartmendId=compartmentId, displayName=displayName, validation_deployment_details=validation_deployment_details, create_deployment_details=create_deployment_details, api_gateway_id=api_gateway_id) + +# Mudar DisplayName e text_file para poder executar +process_api_spec() + +# data = getSpec() +# fullSpec = json.loads(data) +# print(fullSpec["paths"]["/v1/credit-rights/{internal_number}"]["get"]["operationId"]) diff --git a/files/test/requirements.txt b/files/test/requirements.txt new file mode 100644 index 0000000..500c823 --- /dev/null +++ b/files/test/requirements.txt @@ -0,0 +1,9 @@ +fdk>=0.1.54 +requests +oci +cryptography +six +PyJWT +py3_lru_cache +simplejson +PyYAML diff --git a/images/api_id_response.png b/images/api_id_response.png new file mode 100644 index 0000000..74c8839 Binary files /dev/null and b/images/api_id_response.png differ diff --git a/images/authApi_1.png b/images/authApi_1.png new file mode 100644 index 0000000..81bf115 Binary files /dev/null and b/images/authApi_1.png differ diff --git a/images/authApi_2.png b/images/authApi_2.png new file mode 100644 index 0000000..d465797 Binary files /dev/null and b/images/authApi_2.png differ diff --git a/images/authApi_3.png b/images/authApi_3.png new file mode 100644 index 0000000..dc50960 Binary files /dev/null and b/images/authApi_3.png differ diff --git a/images/authApi_4.png b/images/authApi_4.png new file mode 100644 index 0000000..6becba4 Binary files /dev/null and b/images/authApi_4.png differ diff --git a/images/base64translatecode.png b/images/base64translatecode.png new file mode 100644 index 0000000..f8668b6 Binary files /dev/null and b/images/base64translatecode.png differ diff --git a/images/config.png b/images/config.png new file mode 100644 index 0000000..734799c Binary files /dev/null and b/images/config.png differ diff --git a/images/idcs-config.png b/images/idcs-config.png new file mode 100644 index 0000000..ed7fce1 Binary files /dev/null and b/images/idcs-config.png differ diff --git a/images/img-18e.png b/images/img-18e.png new file mode 100644 index 0000000..559dd12 Binary files /dev/null and b/images/img-18e.png differ diff --git a/images/img.png b/images/img.png new file mode 100644 index 0000000..b19290b Binary files /dev/null and b/images/img.png differ diff --git a/images/img_1.png b/images/img_1.png new file mode 100644 index 0000000..0a7de50 Binary files /dev/null and b/images/img_1.png differ diff --git a/images/img_10.png b/images/img_10.png new file mode 100644 index 0000000..d3e4f33 Binary files /dev/null and b/images/img_10.png differ diff --git a/images/img_11.png b/images/img_11.png new file mode 100644 index 0000000..4f5136d Binary files /dev/null and b/images/img_11.png differ diff --git a/images/img_12.png b/images/img_12.png new file mode 100644 index 0000000..4f5136d Binary files /dev/null and b/images/img_12.png differ diff --git a/images/img_13.png b/images/img_13.png new file mode 100644 index 0000000..cd0aaeb Binary files /dev/null and b/images/img_13.png differ diff --git a/images/img_14.png b/images/img_14.png new file mode 100644 index 0000000..f7d5c04 Binary files /dev/null and b/images/img_14.png differ diff --git a/images/img_15.png b/images/img_15.png new file mode 100644 index 0000000..1ce2497 Binary files /dev/null and b/images/img_15.png differ diff --git a/images/img_16.png b/images/img_16.png new file mode 100644 index 0000000..ae3617a Binary files /dev/null and b/images/img_16.png differ diff --git a/images/img_17.png b/images/img_17.png new file mode 100644 index 0000000..13d39cb Binary files /dev/null and b/images/img_17.png differ diff --git a/images/img_18.png b/images/img_18.png new file mode 100644 index 0000000..dd6773e Binary files /dev/null and b/images/img_18.png differ diff --git a/images/img_18a.png b/images/img_18a.png new file mode 100644 index 0000000..40d9876 Binary files /dev/null and b/images/img_18a.png differ diff --git a/images/img_18c.png b/images/img_18c.png new file mode 100644 index 0000000..029dc68 Binary files /dev/null and b/images/img_18c.png differ diff --git a/images/img_18d.png b/images/img_18d.png new file mode 100644 index 0000000..6224cbc Binary files /dev/null and b/images/img_18d.png differ diff --git a/images/img_19.png b/images/img_19.png new file mode 100644 index 0000000..73cbaf4 Binary files /dev/null and b/images/img_19.png differ diff --git a/images/img_2.png b/images/img_2.png new file mode 100644 index 0000000..d8cd199 Binary files /dev/null and b/images/img_2.png differ diff --git a/images/img_20.png b/images/img_20.png new file mode 100644 index 0000000..73cbaf4 Binary files /dev/null and b/images/img_20.png differ diff --git a/images/img_21.png b/images/img_21.png new file mode 100644 index 0000000..66ce870 Binary files /dev/null and b/images/img_21.png differ diff --git a/images/img_3.png b/images/img_3.png new file mode 100644 index 0000000..d58049e Binary files /dev/null and b/images/img_3.png differ diff --git a/images/img_4.png b/images/img_4.png new file mode 100644 index 0000000..11db0e6 Binary files /dev/null and b/images/img_4.png differ diff --git a/images/img_5.png b/images/img_5.png new file mode 100644 index 0000000..70f6468 Binary files /dev/null and b/images/img_5.png differ diff --git a/images/img_6.png b/images/img_6.png new file mode 100644 index 0000000..932f860 Binary files /dev/null and b/images/img_6.png differ diff --git a/images/img_7.png b/images/img_7.png new file mode 100644 index 0000000..bcf774b Binary files /dev/null and b/images/img_7.png differ diff --git a/images/img_8.png b/images/img_8.png new file mode 100644 index 0000000..6c3561e Binary files /dev/null and b/images/img_8.png differ diff --git a/images/img_9.png b/images/img_9.png new file mode 100644 index 0000000..216ab85 Binary files /dev/null and b/images/img_9.png differ diff --git a/images/img_api_id.png b/images/img_api_id.png new file mode 100644 index 0000000..e0e594c Binary files /dev/null and b/images/img_api_id.png differ diff --git a/images/import-bravado.png b/images/import-bravado.png new file mode 100644 index 0000000..9645d3d Binary files /dev/null and b/images/import-bravado.png differ diff --git a/images/json_yaml_format.png b/images/json_yaml_format.png new file mode 100644 index 0000000..5d1ac9e Binary files /dev/null and b/images/json_yaml_format.png differ diff --git a/images/json_yaml_format_1.png b/images/json_yaml_format_1.png new file mode 100644 index 0000000..c67d42f Binary files /dev/null and b/images/json_yaml_format_1.png differ diff --git a/images/main-redact-routine.png b/images/main-redact-routine.png new file mode 100644 index 0000000..43f7048 Binary files /dev/null and b/images/main-redact-routine.png differ diff --git a/images/proxy-real-config.png b/images/proxy-real-config.png new file mode 100644 index 0000000..68b655d Binary files /dev/null and b/images/proxy-real-config.png differ diff --git a/images/redact-code.png b/images/redact-code.png new file mode 100644 index 0000000..ad2386c Binary files /dev/null and b/images/redact-code.png differ diff --git a/images/redact-response.png b/images/redact-response.png new file mode 100644 index 0000000..9a63c37 Binary files /dev/null and b/images/redact-response.png differ diff --git a/images/redaction-1.png b/images/redaction-1.png new file mode 100644 index 0000000..ead5f9c Binary files /dev/null and b/images/redaction-1.png differ diff --git a/images/redaction-2.png b/images/redaction-2.png new file mode 100644 index 0000000..25fdc64 Binary files /dev/null and b/images/redaction-2.png differ diff --git a/images/redaction-3.png b/images/redaction-3.png new file mode 100644 index 0000000..1408d71 Binary files /dev/null and b/images/redaction-3.png differ diff --git a/images/redaction-4.png b/images/redaction-4.png new file mode 100644 index 0000000..4b890fa Binary files /dev/null and b/images/redaction-4.png differ diff --git a/images/resourceprincipal-1.png b/images/resourceprincipal-1.png new file mode 100644 index 0000000..64cb0e7 Binary files /dev/null and b/images/resourceprincipal-1.png differ diff --git a/images/resourceprincipal-2.png b/images/resourceprincipal-2.png new file mode 100644 index 0000000..58c6d8b Binary files /dev/null and b/images/resourceprincipal-2.png differ diff --git a/images/resourceprincipal-3.png b/images/resourceprincipal-3.png new file mode 100644 index 0000000..1aa28c2 Binary files /dev/null and b/images/resourceprincipal-3.png differ diff --git a/images/resourceprincipal-4.png b/images/resourceprincipal-4.png new file mode 100644 index 0000000..fb28271 Binary files /dev/null and b/images/resourceprincipal-4.png differ diff --git a/images/resourceprincipal-5.png b/images/resourceprincipal-5.png new file mode 100644 index 0000000..3a1e0a1 Binary files /dev/null and b/images/resourceprincipal-5.png differ diff --git a/images/vault-1.png b/images/vault-1.png new file mode 100644 index 0000000..bbd2707 Binary files /dev/null and b/images/vault-1.png differ