First Commit

This commit is contained in:
2024-01-14 19:24:30 -03:00
commit f5a8be69ca
62 changed files with 1739 additions and 0 deletions

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Zeppelin ignored files
/ZeppelinRemoteNotebooks/

9
.idea/OCI_API_Gateway_Automation2.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

17
.idea/aws.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="accountSettings">
<option name="activeProfile" value="profile:default" />
<option name="activeRegion" value="us-east-2" />
<option name="recentlyUsedProfiles">
<list>
<option value="profile:default" />
</list>
</option>
<option name="recentlyUsedRegions">
<list>
<option value="us-east-2" />
</list>
</option>
</component>
</project>

7
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<ScalaCodeStyleSettings>
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
</ScalaCodeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

9
.idea/markdown.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<enabledExtensions>
<entry key="MermaidLanguageExtension" value="true" />
<entry key="PlantUMLLanguageExtension" value="true" />
</enabledExtensions>
</component>
</project>

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_18" default="true" project-jdk-name="18" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/OCI_API_Gateway_Automation2.iml" filepath="$PROJECT_DIR$/.idea/OCI_API_Gateway_Automation2.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

243
README.md Normal file
View File

@@ -0,0 +1,243 @@
# 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)
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)
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: <YOUR OAUTH2 TOKEN>' \
--header 'displayName: EXEMPLO-2024-01-01' \
--header 'apiCompartmentId: ocid1.compartment.oc1..aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' \
--header 'Content-Type: application/json' \
--data-raw '<YOUR OPENAPI SPEC>'
>**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_1.png](images/authApi_2.png)
Authorization function works 2 times
The first call to the authorization function needs to validate your token from the **IDCS** and the first call always came with **body_schema_validation** = None.
In the second call, the **body_schema_validation** came with some schema value from your OpenAPI spec, so the **IDCS** validation will be skiped.
![img_2.png](images/authApi_3.png)
In the first authorization execution, the validation step will be skiped but in the second execution, the validation occurs with the same logic in the **body_schema_validation**.
![img_3.png](images/authApi_4.png)
## 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_8.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_18a.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**: 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
And you can test with:
curl --location 'https://xxxxxxxxxxxxxxxxxxxxxxx.apigateway.us-ashburn-1.oci.customer-oci.com/applyValidationApi/apply' \
--header 'token: <YOUR OAUTH2 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: xxxxxxxxxxxxxxxxxxxxxxx.apigateway.us-ashburn-1.oci.customer-oci.com' \
--header 'Content-Type: application/json' \
--data-raw '<YOUR OPENAPI SPEC>'
## 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
**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
## 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)

Binary file not shown.

View File

@@ -0,0 +1,100 @@
{
"compartment_id": "<API Gateway Compartment OCID>",
"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": "<applyValidationApi function OCID>",
"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]"
},
"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"
}

View File

@@ -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

View File

@@ -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"
}

View File

@@ -0,0 +1,523 @@
import base64
import json
import io
from fdk import response
import oci
import requests
import time
from itertools import groupby
#### 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 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):
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))
if (gateway["items"] != []):
gateway_id = gateway["items"][0]["gateway_id"]
deployment_id = gateway["items"][0]["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):
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))
if (gateway["items"] != []):
gateway_id = gateway["items"][0]["gateway_id"]
deployment_id = gateway["items"][0]["id"]
else:
gateway_id = api_gateway_id
deployment_id = 0
path_prefix = "/"
routes = [ ]
new_routes = [ ]
for item in payload:
methods = json.loads(json.dumps(item["METHOD"].split(" ")))
path_prefix = item["PATH_PREFIX"]
if (item["SCHEMA_BODY_VALIDATION"] != ""):
callback_url = ("https://" + host + item["PATH_PREFIX"] + "validation-callback" + item["PATH"]).replace("{", "${request.path[").replace("}", "]}")
put_logs_response = logging.put_logs(
log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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=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(
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]'},
cache_key=["token"],
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(
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]'},
cache_key=["token"],
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)
if (routes != [ ]):
validation_deployment_details=oci.apigateway.models.UpdateDeploymentDetails(
display_name=displayName,
specification=oci.apigateway.models.ApiSpecification(
request_policies=oci.apigateway.models.ApiSpecificationRequestPolicies(
authentication=oci.apigateway.models.CustomAuthenticationPolicy(
type="CUSTOM_AUTHENTICATION",
function_id=functionId,
is_anonymous_access_allowed=False,
parameters={
'token': 'request.headers[token]',
'body': 'request.body'},
cache_key=["token"])),
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(
authentication=oci.apigateway.models.CustomAuthenticationPolicy(
type="CUSTOM_AUTHENTICATION",
function_id=functionId,
is_anonymous_access_allowed=False,
parameters={
'token': 'request.headers[token]',
'body': 'request.body'},
cache_key=["token"])),
routes=routes))
creeateOrUpdateDeployment(compartmendId=compartmentId, displayName=displayName, validation_deployment_details=validation_deployment_details, create_deployment_details=create_deployment_details)
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"]:
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"]:
METHOD = (METHOD + " " + method).lstrip().upper()
return METHOD
def check_endpoint(endpoint):
if (endpoint.find("http://") == -1 and endpoint.find("https://") == -1):
endpoint = "https://" + endpoint
return endpoint
def key_func(k):
return k['PATH']
def group_by(payload):
config = oci.config.from_file("config")
logging = oci.loggingingestion.LoggingClient(config)
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 process_api_spec(api_id, compartmentId, environment, swagger, functionId, host, api_gateway_id):
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 = []
for spec in api_spec["routes"]:
status = spec["backend"]["status"]
if (version == "3"):
fullEndpoint = (endPoint + find_base_path(spec["path"]) + find_path(spec["path"])).replace("{", "${request.path[").replace("}", "]}")
FULL_PATH = spec["path"]
ENDPOINT = fullEndpoint
PATH = find_path(spec["path"])
PATH_PREFIX = find_base_path(spec["path"])
METHOD = accMethods(api_spec["routes"], PATH, status)
else:
fullEndpoint = check_endpoint((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)
put_logs_response = logging.put_logs(
log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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,
'CONTENT_TYPE': CONTENT_TYPE
}),
id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")],
source="EXAMPLE-source-Value",
type="EXAMPLE-type-Value")]))
payload = json.dumps(json_data_list)
json_data_list = { each['PATH'] : each for each in json_data_list}.values()
put_logs_response = logging.put_logs(
log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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: " + str(json_data_list),
id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")],
source="EXAMPLE-source-Value",
type="EXAMPLE-type-Value")]))
if (version == "2"):
payload = json.loads(json.dumps(group_by(payload)))
json_data_list = { each['PATH'] : each for each in payload}.values()
print(payload)
applyAuthApi(compartmentId=compartmentId, displayName=API_NAME, payload=json_data_list, functionId=functionId, host=host, api_gateway_id=api_gateway_id)
except(Exception) as ex:
jsonData = 'error parsing json payload: ' + str(ex)
put_logs_response = logging.put_logs(
log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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(3): " + jsonData,
id="ocid1.test.oc1..00000001.EXAMPLE-id-Value")],
source="EXAMPLE-source-Value",
type="EXAMPLE-type-Value")]))
###
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)
body = json.loads(body)
environment = "DEV" #for future development
# header values
access_token = header["token"]
api_id = header["apiId"]
host = header["host"]
compartmentId = header['apiCompartmentId']
functionId = header['functionId']
api_gateway_id = header['apiGatewayId']
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.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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)
rdata = json.dumps({
"active": True,
"context": {
"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.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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})
)

View File

@@ -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

View File

@@ -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-----

View File

@@ -0,0 +1,9 @@
fdk>=0.1.54
requests
oci
cryptography
six
PyJWT
py3_lru_cache
simplejson

7
files/authApi/config Normal file
View File

@@ -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

12
files/authApi/config.json Normal file
View File

@@ -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"
}

237
files/authApi/func.py Normal file
View File

@@ -0,0 +1,237 @@
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
#### 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 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"
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"]
if (body_schema_validation == None):
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"]):
# Version OpenAPI 3
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))
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:
# Version Swagger 2
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 = Spec.from_dict(spec_dict, config=bravado_config)
schema = spec_dict["definitions"][contents[0]]
validate_object(spec, schema, body)
except (Exception) as ex3:
error_msg = beautify_str(str(ex3))
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})
)

8
files/authApi/func.yaml Normal file
View File

@@ -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

View File

@@ -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-----

View File

@@ -0,0 +1,10 @@
fdk>=0.1.54
requests
oci
cryptography
six
PyJWT
py3_lru_cache
simplejson
openapi-schema-validator
bravado-core

7
files/createapi/config Normal file
View File

@@ -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

View File

@@ -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"
}

View File

@@ -0,0 +1,136 @@
{
"compartment_id": "<API Gateway Compartment OCID>",
"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": "<createApi function OCID>",
"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"
}

233
files/createapi/func.py Normal file
View File

@@ -0,0 +1,233 @@
import base64
import json
import io
from fdk import response
import oci
import requests
#### 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:
jsonData = 'error parsing json payload: ' + str(ex)
put_logs_response = logging.put_logs(
log_id="ocid1.log.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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")]))
###
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)
body = json.loads(body)
environment = "DEV" #for future development
# 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.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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)
}})
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.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
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})
)

View File

@@ -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

View File

@@ -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-----

View File

@@ -0,0 +1,9 @@
fdk>=0.1.54
requests
oci
cryptography
six
PyJWT
py3_lru_cache
simplejson

BIN
images/api_id_response.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
images/authApi_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
images/authApi_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
images/authApi_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
images/authApi_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
images/config.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
images/idcs-config.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
images/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
images/img_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

BIN
images/img_10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
images/img_11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
images/img_12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
images/img_13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
images/img_14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

BIN
images/img_15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
images/img_16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
images/img_17.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
images/img_18.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
images/img_18a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

BIN
images/img_19.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
images/img_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
images/img_20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
images/img_21.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
images/img_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
images/img_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
images/img_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

BIN
images/img_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

BIN
images/img_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

BIN
images/img_8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

BIN
images/img_9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB