├── .gitattributes ├── ballerina ├── icon.png ├── tests │ ├── resources │ │ ├── keystore │ │ │ ├── ballerinaKeystore.p12 │ │ │ └── ballerinaTruststore.p12 │ │ ├── cert │ │ │ ├── public.crt │ │ │ └── wso2Public.crt │ │ └── key │ │ │ ├── private.key │ │ │ └── encryptedPrivate.key │ ├── test_utils.bal │ └── listener_oauth2_provider_test.bal ├── Ballerina.toml ├── README.md ├── oauth2_errors.bal ├── init.bal ├── oauth2_commons.bal ├── Dependencies.toml ├── build.gradle └── listener_oauth2_provider.bal ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── codecov.yml ├── load-tests └── order_management_service │ ├── deployment │ ├── kustomization.yaml │ ├── ingress.yaml │ └── wso2is-sts.yaml │ ├── src │ ├── Ballerina.toml │ ├── Cloud.toml │ ├── resources │ │ ├── api_gateway │ │ │ ├── public.crt │ │ │ └── private.key │ │ └── app_backend │ │ │ ├── public.crt │ │ │ └── private.key │ ├── app_backend.bal │ └── api_gateway.bal │ └── scripts │ ├── run.sh │ └── http-post-request.jmx ├── examples └── order-management-service │ ├── app_backend │ ├── Config.toml │ ├── Ballerina.toml │ ├── resources │ │ ├── public.crt │ │ ├── sts-public.crt │ │ └── private.key │ └── app_backend.bal │ ├── api_gateway │ ├── Ballerina.toml │ ├── resources │ │ ├── public.crt │ │ ├── sts-public.crt │ │ └── private.key │ └── api_gateway.bal │ ├── order-management-service.png │ └── A Guideline on Securing Ballerina REST APIs.md ├── .github ├── pull_request_template.md ├── CODEOWNERS └── workflows │ ├── trivy-scan.yml │ ├── build-timestamped-master.yml │ ├── publish-release.yml │ ├── fossa_scan.yml │ ├── pull-request.yml │ ├── process-load-test-result.yml │ ├── central-publish.yml │ ├── trigger-load-tests.yml │ ├── build-with-bal-test-graalvm.yml │ └── update_spec.yml ├── .gitignore ├── gradle.properties ├── native ├── src │ └── main │ │ └── java │ │ ├── module-info.java │ │ └── io │ │ └── ballerina │ │ └── stdlib │ │ └── oauth2 │ │ ├── ModuleUtils.java │ │ ├── OAuth2Constants.java │ │ └── OAuth2Client.java └── build.gradle ├── settings.gradle ├── gradlew.bat ├── changelog.md ├── README.md ├── gradlew ├── LICENSE └── docs └── spec └── spec.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensure all Java files use LF. 2 | *.java eol=lf 3 | -------------------------------------------------------------------------------- /ballerina/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerina-oauth2/HEAD/ballerina/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerina-oauth2/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: "60...80" 5 | status: 6 | project: 7 | default: 8 | target: 80 9 | -------------------------------------------------------------------------------- /load-tests/order_management_service/deployment/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - order_management_service.yaml 3 | - ingress.yaml 4 | - wso2is-sts.yaml 5 | -------------------------------------------------------------------------------- /examples/order-management-service/app_backend/Config.toml: -------------------------------------------------------------------------------- 1 | [app_backend] 2 | clientId="uDMwA4hKR9H3deeXxvNf4sSU0i4a" 3 | clientSecret="8FOUOKUQfOp47pUfJCsPA5X4clga" 4 | -------------------------------------------------------------------------------- /ballerina/tests/resources/keystore/ballerinaKeystore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerina-oauth2/HEAD/ballerina/tests/resources/keystore/ballerinaKeystore.p12 -------------------------------------------------------------------------------- /ballerina/tests/resources/keystore/ballerinaTruststore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerina-oauth2/HEAD/ballerina/tests/resources/keystore/ballerinaTruststore.p12 -------------------------------------------------------------------------------- /examples/order-management-service/api_gateway/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "oauth2" 3 | name = "api_gateway" 4 | version = "1.0.0" 5 | 6 | [build-options] 7 | observabilityIncluded = true 8 | -------------------------------------------------------------------------------- /examples/order-management-service/app_backend/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "oauth2" 3 | name = "app_backend" 4 | version = "1.0.0" 5 | 6 | [build-options] 7 | observabilityIncluded = true 8 | -------------------------------------------------------------------------------- /examples/order-management-service/order-management-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerina-oauth2/HEAD/examples/order-management-service/order-management-service.png -------------------------------------------------------------------------------- /load-tests/order_management_service/src/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "oauth2" 3 | name = "order_management_service" 4 | version = "1.0.0" 5 | 6 | [build-options] 7 | observabilityIncluded = false 8 | cloud = "k8s" 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | ## Examples 4 | 5 | ## Checklist 6 | - [ ] Linked to an issue 7 | - [ ] Updated the changelog 8 | - [ ] Added tests 9 | - [ ] Updated the spec 10 | - [ ] Checked native-image compatibility 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # See: https://help.github.com/articles/about-codeowners/ 5 | 6 | # These owners will be the default owners for everything in the repo. 7 | * @ThisaruGuruge @MohamedSabthar @DimuthuMadushan @shafreenAnfar 8 | -------------------------------------------------------------------------------- /.github/workflows/trivy-scan.yml: -------------------------------------------------------------------------------- 1 | name: Trivy 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "30 20 * * *" 7 | 8 | jobs: 9 | call_workflow: 10 | name: Run Trivy Scan Workflow 11 | if: ${{ github.repository_owner == 'ballerina-platform' }} 12 | uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@main 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/Cloud.toml: -------------------------------------------------------------------------------- 1 | [container.image] 2 | repository="ballerina" 3 | name="order_management_service" 4 | entrypoint="CMD java -Xdiag -Djdk.internal.httpclient.disableHostnameVerification -cp '${APP}:jars/*' 'oauth2/order_management_service/1/$_init'" 5 | 6 | [cloud.deployment] 7 | min_memory="256Mi" 8 | max_memory="2048Mi" 9 | min_cpu="200m" 10 | max_cpu="2000m" 11 | 12 | [cloud.deployment.autoscaling] 13 | min_replicas=1 14 | max_replicas=1 15 | -------------------------------------------------------------------------------- /.github/workflows/build-timestamped-master.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '*.md' 9 | - 'docs/**' 10 | - 'load-tests/**' 11 | workflow_dispatch: 12 | 13 | jobs: 14 | call_workflow: 15 | name: Run Build Workflow 16 | if: ${{ github.repository_owner == 'ballerina-platform' }} 17 | uses: ballerina-platform/ballerina-library/.github/workflows/build-timestamp-master-template.yml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | workflow_dispatch: 5 | repository_dispatch: 6 | types: [stdlib-release-pipeline] 7 | 8 | jobs: 9 | call_workflow: 10 | name: Run Release Workflow 11 | if: ${{ github.repository_owner == 'ballerina-platform' }} 12 | uses: ballerina-platform/ballerina-library/.github/workflows/release-package-template.yml@main 13 | secrets: inherit 14 | with: 15 | package-name: oauth2 16 | package-org: ballerina 17 | -------------------------------------------------------------------------------- /.github/workflows/fossa_scan.yml: -------------------------------------------------------------------------------- 1 | name: Fossa Scan 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '30 18 * * *' # 00:00 in LK time (GMT+5:30) 6 | jobs: 7 | fossa-scan: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: fossas/fossa-action@main 12 | env: 13 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 14 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 15 | with: 16 | api-key: ${{secrets.FOSSA_APIKEY}} 17 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} 5 | cancel-in-progress: true 6 | 7 | on: pull_request 8 | 9 | jobs: 10 | call_workflow: 11 | name: Run PR Build Workflow 12 | if: ${{ github.repository_owner == 'ballerina-platform' }} 13 | uses: ballerina-platform/ballerina-library/.github/workflows/pull-request-build-template.yml@main 14 | with: 15 | additional-windows-test-flags: '-x test' 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /ballerina/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "ballerina" 3 | name = "oauth2" 4 | version = "2.15.0" 5 | authors = ["Ballerina"] 6 | keywords = ["security", "authorization", "introspection"] 7 | repository = "https://github.com/ballerina-platform/module-ballerina-oauth2" 8 | icon = "icon.png" 9 | license = ["Apache-2.0"] 10 | distribution = "2201.13.0" 11 | 12 | [platform.java21] 13 | graalvmCompatible = true 14 | 15 | [[platform.java21.dependency]] 16 | groupId = "io.ballerina.stdlib" 17 | artifactId = "oauth2-native" 18 | version = "2.15.0" 19 | path = "../native/build/libs/oauth2-native-2.15.0-SNAPSHOT.jar" 20 | -------------------------------------------------------------------------------- /.github/workflows/process-load-test-result.yml: -------------------------------------------------------------------------------- 1 | name: Process load test results 2 | 3 | on: 4 | repository_dispatch: 5 | types: [oauth2-load-test] 6 | 7 | jobs: 8 | call_stdlib_process_load_test_results_workflow: 9 | name: Run StdLib Process Load Test Results Workflow 10 | uses: ballerina-platform/ballerina-library/.github/workflows/process-load-test-results-template.yml@main 11 | with: 12 | results: ${{ toJson(github.event.client_payload.results) }} 13 | secrets: 14 | ballerina_bot_token: ${{ secrets.BALLERINA_BOT_TOKEN }} 15 | ballerina_reviewer_bot_token: ${{ secrets.BALLERINA_REVIEWER_BOT_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/central-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to the Ballerina central 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | environment: 7 | type: choice 8 | description: Select Environment 9 | required: true 10 | options: 11 | - DEV CENTRAL 12 | - STAGE CENTRAL 13 | 14 | jobs: 15 | call_workflow: 16 | name: Run Central Publish Workflow 17 | if: ${{ github.repository_owner == 'ballerina-platform' }} 18 | uses: ballerina-platform/ballerina-library/.github/workflows/central-publish-template.yml@main 19 | secrets: inherit 20 | with: 21 | environment: ${{ github.event.inputs.environment }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | build 26 | .gradle/ 27 | target 28 | # IDEA Files 29 | .idea/ 30 | *.iml 31 | *.ipr 32 | *.iws 33 | 34 | # MacOS 35 | *.DS_Store 36 | 37 | # Ballerina 38 | velocity.log* 39 | *Ballerina.lock 40 | 41 | # Auto generated Dependencies.toml files 42 | examples/**/Dependencies.toml 43 | load-tests/**/Dependencies.toml 44 | -------------------------------------------------------------------------------- /load-tests/order_management_service/deployment/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | name: order-management-service 6 | annotations: 7 | kubernetes.io/ingress.class: nginx 8 | nginx.ingress.kubernetes.io/ssl-passthrough: "true" 9 | nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" 10 | spec: 11 | rules: 12 | - host: bal.perf.test 13 | http: 14 | paths: 15 | - path: "/" 16 | pathType: Prefix 17 | backend: 18 | service: 19 | name: order-managemen 20 | port: 21 | number: 9090 22 | tls: 23 | - hosts: 24 | - "bal.perf.test" 25 | -------------------------------------------------------------------------------- /load-tests/order_management_service/deployment/wso2is-sts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "apps/v1" 2 | kind: Deployment 3 | metadata: 4 | name: wso2is-sts 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: wso2is-sts 10 | template: 11 | metadata: 12 | labels: 13 | app: wso2is-sts 14 | logs: "true" 15 | spec: 16 | containers: 17 | - name: wso2is-container 18 | image: ldclakmal/wso2is-sts:latest 19 | ports: 20 | - containerPort: 9443 21 | 22 | --- 23 | apiVersion: v1 24 | kind: Service 25 | metadata: 26 | name: wso2is-sts-service 27 | spec: 28 | type: ClusterIP 29 | ports: 30 | - port: 9443 31 | selector: 32 | app: wso2is-sts 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | group=io.ballerina.stdlib 3 | version=2.15.0-SNAPSHOT 4 | ballerinaLangVersion=2201.13.0-20251015-051600-27035519 5 | 6 | checkstylePluginVersion=10.12.0 7 | spotbugsPluginVersion=6.0.18 8 | shadowJarPluginVersion=8.1.1 9 | downloadPluginVersion=5.4.0 10 | releasePluginVersion=2.8.0 11 | ballerinaGradlePluginVersion=2.3.0 12 | 13 | # Dependencies 14 | # Level 01 15 | stdlibIoVersion=1.8.0 16 | stdlibTimeVersion=2.8.0-20251015-144800-cc738de 17 | stdlibUrlVersion=2.6.0 18 | stdlibUuidVersion=1.10.0 19 | 20 | # Level 02 21 | stdlibConstraintVersion=1.7.0 22 | stdlibCryptoVersion=2.9.1-20250925-191400-cd7bbcf 23 | stdlibLogVersion=2.13.0 24 | stdlibTaskVersion=2.11.0 25 | 26 | # Level 03 27 | stdlibCacheVersion=3.10.0 28 | 29 | # Ballerina Observe 30 | observeVersion=1.5.0 31 | observeInternalVersion=1.5.0 32 | -------------------------------------------------------------------------------- /native/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | module io.ballerina.stdlib.oauth2 { 19 | requires io.ballerina.runtime; 20 | requires java.net.http; 21 | requires io.ballerina.stdlib.crypto; 22 | exports io.ballerina.stdlib.oauth2; 23 | } 24 | -------------------------------------------------------------------------------- /load-tests/order_management_service/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Copyright 2021 WSO2 Inc. (http://wso2.org) 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # ---------------------------------------------------------------------------- 17 | # Execution script for ballerina performance tests 18 | # ---------------------------------------------------------------------------- 19 | set -e 20 | source base-scenario.sh 21 | 22 | jmeter -n -t "$scriptsDir/"http-post-request.jmx -l "$resultsDir/"original.jtl -Jusers="$concurrent_users" -Jduration=3600 -Jhost=bal.perf.test -Jport=443 -Jprotocol=https -Jpath=order $payload_flags 23 | -------------------------------------------------------------------------------- /ballerina/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This module provides a framework for interacting with OAuth2 authorization servers as specified in the [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) and [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662). 4 | 5 | The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service or by allowing the third-party application to obtain access on its own behalf. 6 | 7 | The Ballerina `oauth2` module facilitates auth providers that are to be used by the clients and listeners of different protocol connectors. 8 | 9 | ### Listener OAuth2 provider 10 | 11 | Represents the listener OAuth2 provider, which is used to validate the received credential (access token) by calling the configured OAuth2 introspection endpoint. 12 | 13 | ### Client OAuth2 provider 14 | 15 | Represents the client OAuth2 provider, which is used to generate OAuth2 access tokens using the configured OAuth2 token endpoint configurations. This supports the client credentials grant type, password grant type, and refresh token grant type. 16 | -------------------------------------------------------------------------------- /.github/workflows/trigger-load-tests.yml: -------------------------------------------------------------------------------- 1 | name: Trigger load tests 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tests: 7 | description: > 8 | List of test names. This needs to be filled only if you want to run a specific set of tests. Example: foo,bar 9 | required: false 10 | clusterName: 11 | description: 'Cluster name' 12 | default: 'oauth2-perf-cluster-test' 13 | required: false 14 | schedule: 15 | - cron: '30 16 * * *' 16 | 17 | jobs: 18 | call_stdlib_trigger_load_test_workflow: 19 | name: Run StdLib Load Test Workflow 20 | if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} 21 | uses: ballerina-platform/ballerina-library/.github/workflows/trigger-load-tests-template.yml@main 22 | with: 23 | repo_name: 'module-ballerina-oauth2' 24 | runtime_artifacts_url: 'https://api.github.com/repos/ballerina-platform/module-ballerina-oauth2/actions/artifacts' 25 | dispatch_type: 'oauth2-load-test' 26 | cluster_name: ${{ inputs.clusterName }} 27 | tests: ${{ inputs.tests }} 28 | branch: ${{ inputs.branch }} 29 | secrets: 30 | ballerina_bot_token: ${{ secrets.BALLERINA_BOT_TOKEN }} 31 | -------------------------------------------------------------------------------- /ballerina/tests/resources/cert/public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdzCCAl+gAwIBAgIEfP3e8zANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxDTALBgNVBAoT 4 | BFdTTzIxDTALBgNVBAsTBFdTTzIxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEw 5 | MjQwNTQ3NThaFw0zNzEwMTkwNTQ3NThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzENMAsGA1UEChMEV1NPMjENMAsG 7 | A1UECxMEV1NPMjESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAgVyi6fViVLiZKEnw59xzNi1lcYh6z9dZnug+F9gKqFIg 9 | mdcPe+qtS7gZc1jYTjWMCbx13sFLkZqNHeDUadpmtKo3TDduOl1sqM6cz3yXb6L3 10 | 4k/leh50mzIPNmaaXxd3vOQoK4OpkgO1n32mh6+tkp3sbHmfYqDQrkVK1tmYNtPJ 11 | ffSCLT+CuIhnJUJco7N0unax+ySZN67/AX++sJpqAhAIZJzrRi6ueN3RFCIxYDXS 12 | MvxrEmOdn4gOC0o1Ar9u5Bp9N52sqqGbN1x6jNKi3bfUj122Hu5e+Y9KOmfbchhQ 13 | il2P81cIi30VKgyDn5DeWEuDoYredk4+6qAZrxMw+wIDAQABozEwLzAOBgNVHQ8B 14 | Af8EBAMCBaAwHQYDVR0OBBYEFNmtrQ36j6tUGhKrfW9qWWE7KFzMMA0GCSqGSIb3 15 | DQEBCwUAA4IBAQAv3yOwgbtOu76eJMl1BCcgTFgaMUBZoUjK9Un6HGjKEgYz/YWS 16 | ZFlY/qH5rT01DWQevUZB626d5ZNdzSBZRlpsxbf9IE/ursNHwHx9ua6fB7yHUCzC 17 | 1ZMp1lvBHABi7wcA+5nbV6zQ7HDmBXFhJfbgH1iVmA1KcvDeBPSJ/scRGasZ5q2W 18 | 3IenDNrfPIUhD74tFiCiqNJO91qD/LO+++3XeZzfPh8NRKkiPX7dB8WJ3YNBuQAv 19 | gRWTISpSSXLmqMb+7MPQVgecsepZdk8CwkRLxh3RKPJMjigmCgyvkSaoDMKAYC3i 20 | YjfUTiJ57UeqoSl0IaOFJ0wfZRFh+UytlDZa 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /ballerina/oauth2_errors.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/log; 18 | 19 | # Represents the error type of the module. This will be returned if an error occurred while the listener OAuth2 provider 20 | # tries to validate the received credentials and the client OAuth2 provider tries to generate the token. 21 | public type Error distinct error; 22 | 23 | // Logs and prepares the `error` as an `oauth2:Error`. 24 | isolated function prepareError(string message, error? err = ()) returns Error { 25 | log:printDebug(message, 'error = err); 26 | if err is error { 27 | return error Error(message, err); 28 | } 29 | return error Error(message); 30 | } 31 | -------------------------------------------------------------------------------- /examples/order-management-service/api_gateway/resources/public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdzCCAl+gAwIBAgIEfP3e8zANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxDTALBgNVBAoT 4 | BFdTTzIxDTALBgNVBAsTBFdTTzIxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEw 5 | MjQwNTQ3NThaFw0zNzEwMTkwNTQ3NThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzENMAsGA1UEChMEV1NPMjENMAsG 7 | A1UECxMEV1NPMjESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAgVyi6fViVLiZKEnw59xzNi1lcYh6z9dZnug+F9gKqFIg 9 | mdcPe+qtS7gZc1jYTjWMCbx13sFLkZqNHeDUadpmtKo3TDduOl1sqM6cz3yXb6L3 10 | 4k/leh50mzIPNmaaXxd3vOQoK4OpkgO1n32mh6+tkp3sbHmfYqDQrkVK1tmYNtPJ 11 | ffSCLT+CuIhnJUJco7N0unax+ySZN67/AX++sJpqAhAIZJzrRi6ueN3RFCIxYDXS 12 | MvxrEmOdn4gOC0o1Ar9u5Bp9N52sqqGbN1x6jNKi3bfUj122Hu5e+Y9KOmfbchhQ 13 | il2P81cIi30VKgyDn5DeWEuDoYredk4+6qAZrxMw+wIDAQABozEwLzAOBgNVHQ8B 14 | Af8EBAMCBaAwHQYDVR0OBBYEFNmtrQ36j6tUGhKrfW9qWWE7KFzMMA0GCSqGSIb3 15 | DQEBCwUAA4IBAQAv3yOwgbtOu76eJMl1BCcgTFgaMUBZoUjK9Un6HGjKEgYz/YWS 16 | ZFlY/qH5rT01DWQevUZB626d5ZNdzSBZRlpsxbf9IE/ursNHwHx9ua6fB7yHUCzC 17 | 1ZMp1lvBHABi7wcA+5nbV6zQ7HDmBXFhJfbgH1iVmA1KcvDeBPSJ/scRGasZ5q2W 18 | 3IenDNrfPIUhD74tFiCiqNJO91qD/LO+++3XeZzfPh8NRKkiPX7dB8WJ3YNBuQAv 19 | gRWTISpSSXLmqMb+7MPQVgecsepZdk8CwkRLxh3RKPJMjigmCgyvkSaoDMKAYC3i 20 | YjfUTiJ57UeqoSl0IaOFJ0wfZRFh+UytlDZa 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /examples/order-management-service/app_backend/resources/public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdzCCAl+gAwIBAgIEfP3e8zANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxDTALBgNVBAoT 4 | BFdTTzIxDTALBgNVBAsTBFdTTzIxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEw 5 | MjQwNTQ3NThaFw0zNzEwMTkwNTQ3NThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzENMAsGA1UEChMEV1NPMjENMAsG 7 | A1UECxMEV1NPMjESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAgVyi6fViVLiZKEnw59xzNi1lcYh6z9dZnug+F9gKqFIg 9 | mdcPe+qtS7gZc1jYTjWMCbx13sFLkZqNHeDUadpmtKo3TDduOl1sqM6cz3yXb6L3 10 | 4k/leh50mzIPNmaaXxd3vOQoK4OpkgO1n32mh6+tkp3sbHmfYqDQrkVK1tmYNtPJ 11 | ffSCLT+CuIhnJUJco7N0unax+ySZN67/AX++sJpqAhAIZJzrRi6ueN3RFCIxYDXS 12 | MvxrEmOdn4gOC0o1Ar9u5Bp9N52sqqGbN1x6jNKi3bfUj122Hu5e+Y9KOmfbchhQ 13 | il2P81cIi30VKgyDn5DeWEuDoYredk4+6qAZrxMw+wIDAQABozEwLzAOBgNVHQ8B 14 | Af8EBAMCBaAwHQYDVR0OBBYEFNmtrQ36j6tUGhKrfW9qWWE7KFzMMA0GCSqGSIb3 15 | DQEBCwUAA4IBAQAv3yOwgbtOu76eJMl1BCcgTFgaMUBZoUjK9Un6HGjKEgYz/YWS 16 | ZFlY/qH5rT01DWQevUZB626d5ZNdzSBZRlpsxbf9IE/ursNHwHx9ua6fB7yHUCzC 17 | 1ZMp1lvBHABi7wcA+5nbV6zQ7HDmBXFhJfbgH1iVmA1KcvDeBPSJ/scRGasZ5q2W 18 | 3IenDNrfPIUhD74tFiCiqNJO91qD/LO+++3XeZzfPh8NRKkiPX7dB8WJ3YNBuQAv 19 | gRWTISpSSXLmqMb+7MPQVgecsepZdk8CwkRLxh3RKPJMjigmCgyvkSaoDMKAYC3i 20 | YjfUTiJ57UeqoSl0IaOFJ0wfZRFh+UytlDZa 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/resources/api_gateway/public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdzCCAl+gAwIBAgIEfP3e8zANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxDTALBgNVBAoT 4 | BFdTTzIxDTALBgNVBAsTBFdTTzIxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEw 5 | MjQwNTQ3NThaFw0zNzEwMTkwNTQ3NThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzENMAsGA1UEChMEV1NPMjENMAsG 7 | A1UECxMEV1NPMjESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAgVyi6fViVLiZKEnw59xzNi1lcYh6z9dZnug+F9gKqFIg 9 | mdcPe+qtS7gZc1jYTjWMCbx13sFLkZqNHeDUadpmtKo3TDduOl1sqM6cz3yXb6L3 10 | 4k/leh50mzIPNmaaXxd3vOQoK4OpkgO1n32mh6+tkp3sbHmfYqDQrkVK1tmYNtPJ 11 | ffSCLT+CuIhnJUJco7N0unax+ySZN67/AX++sJpqAhAIZJzrRi6ueN3RFCIxYDXS 12 | MvxrEmOdn4gOC0o1Ar9u5Bp9N52sqqGbN1x6jNKi3bfUj122Hu5e+Y9KOmfbchhQ 13 | il2P81cIi30VKgyDn5DeWEuDoYredk4+6qAZrxMw+wIDAQABozEwLzAOBgNVHQ8B 14 | Af8EBAMCBaAwHQYDVR0OBBYEFNmtrQ36j6tUGhKrfW9qWWE7KFzMMA0GCSqGSIb3 15 | DQEBCwUAA4IBAQAv3yOwgbtOu76eJMl1BCcgTFgaMUBZoUjK9Un6HGjKEgYz/YWS 16 | ZFlY/qH5rT01DWQevUZB626d5ZNdzSBZRlpsxbf9IE/ursNHwHx9ua6fB7yHUCzC 17 | 1ZMp1lvBHABi7wcA+5nbV6zQ7HDmBXFhJfbgH1iVmA1KcvDeBPSJ/scRGasZ5q2W 18 | 3IenDNrfPIUhD74tFiCiqNJO91qD/LO+++3XeZzfPh8NRKkiPX7dB8WJ3YNBuQAv 19 | gRWTISpSSXLmqMb+7MPQVgecsepZdk8CwkRLxh3RKPJMjigmCgyvkSaoDMKAYC3i 20 | YjfUTiJ57UeqoSl0IaOFJ0wfZRFh+UytlDZa 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/resources/app_backend/public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdzCCAl+gAwIBAgIEfP3e8zANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxDTALBgNVBAoT 4 | BFdTTzIxDTALBgNVBAsTBFdTTzIxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEw 5 | MjQwNTQ3NThaFw0zNzEwMTkwNTQ3NThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzENMAsGA1UEChMEV1NPMjENMAsG 7 | A1UECxMEV1NPMjESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAgVyi6fViVLiZKEnw59xzNi1lcYh6z9dZnug+F9gKqFIg 9 | mdcPe+qtS7gZc1jYTjWMCbx13sFLkZqNHeDUadpmtKo3TDduOl1sqM6cz3yXb6L3 10 | 4k/leh50mzIPNmaaXxd3vOQoK4OpkgO1n32mh6+tkp3sbHmfYqDQrkVK1tmYNtPJ 11 | ffSCLT+CuIhnJUJco7N0unax+ySZN67/AX++sJpqAhAIZJzrRi6ueN3RFCIxYDXS 12 | MvxrEmOdn4gOC0o1Ar9u5Bp9N52sqqGbN1x6jNKi3bfUj122Hu5e+Y9KOmfbchhQ 13 | il2P81cIi30VKgyDn5DeWEuDoYredk4+6qAZrxMw+wIDAQABozEwLzAOBgNVHQ8B 14 | Af8EBAMCBaAwHQYDVR0OBBYEFNmtrQ36j6tUGhKrfW9qWWE7KFzMMA0GCSqGSIb3 15 | DQEBCwUAA4IBAQAv3yOwgbtOu76eJMl1BCcgTFgaMUBZoUjK9Un6HGjKEgYz/YWS 16 | ZFlY/qH5rT01DWQevUZB626d5ZNdzSBZRlpsxbf9IE/ursNHwHx9ua6fB7yHUCzC 17 | 1ZMp1lvBHABi7wcA+5nbV6zQ7HDmBXFhJfbgH1iVmA1KcvDeBPSJ/scRGasZ5q2W 18 | 3IenDNrfPIUhD74tFiCiqNJO91qD/LO+++3XeZzfPh8NRKkiPX7dB8WJ3YNBuQAv 19 | gRWTISpSSXLmqMb+7MPQVgecsepZdk8CwkRLxh3RKPJMjigmCgyvkSaoDMKAYC3i 20 | YjfUTiJ57UeqoSl0IaOFJ0wfZRFh+UytlDZa 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /ballerina/tests/resources/cert/wso2Public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDqTCCApGgAwIBAgIEYfEVSjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxDTALBgNVBAoM 4 | BFdTTzIxDTALBgNVBAsMBFdTTzIxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjAx 5 | MjYwOTMyNThaFw0yNDA0MzAwOTMyNThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | DAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzENMAsGA1UECgwEV1NPMjENMAsG 7 | A1UECwwEV1NPMjESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAkdgncoCrz655Lq8pTdX07eoVBjdZDCUE6ueBd0D1hpJ0 9 | /zE3x3Az6tlvzs98PsPuGzaQOMmuLa4qxNJ+OKxJmutDUlClpuvxuf+jyq4gCV5t 10 | EIILWRMBjlBEpJfWm63+VKKU4nvBWNJ7KfhWjl8+DUdNSh2pCDLpUObmb9Kquqc1 11 | x4BgttjN4rx/P+3/v+1jETXzIP1L44yHtpQNv0khYf4j/aHjcEri9ykvpz1mtdac 12 | brKK25N4V1HHRwDqZiJzOCCISXDuqB6wguY/v4n0l1XtrEs7iCyfRFwNSKNrLqr2 13 | 3tR1CscmLfbH6ZLg5CYJTD+1uPSx0HMOB4Wv51PbWwIDAQABo2MwYTAUBgNVHREE 14 | DTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFH0KQ3YTZJxTsNsPyrZOSFgXXhG+MB0G 15 | A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjALBgNVHQ8EBAMCBPAwDQYJKoZI 16 | hvcNAQELBQADggEBAFNJ34CIiIlCxmyp27+KA224LaHVtL5DucFK0P22FQ+QKkON 17 | iUwO70KoVFreBH1Smxu4ePWk6rMZFOM5oL8HXYg3twy+5eGcL3PQd7X5dwAqlViv 18 | zokoi6SDaA/bIG6J/O1U9Qd4XEVJdVuLqjk1+cp70ALt0X6B7sNLfjFcbz3jQULN 19 | nK8HNvqbn7zQuP10s8p5y2qVkPBA/pjigRDsIWR6p78QESF+TaHFjxfcD6f9cnYi 20 | e+yEHERtG8k8x5jLFe+odI1/QGZP8Fy0oKT+E/TJ1FBh4rB1FtKylqGeauPu89Dn 21 | aJ9+kvpNQ94yFmEuhtDByvDijxAqvlin3TPIfy8= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /examples/order-management-service/api_gateway/resources/sts-public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDqTCCApGgAwIBAgIEYfEVSjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxDTALBgNVBAoM 4 | BFdTTzIxDTALBgNVBAsMBFdTTzIxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjAx 5 | MjYwOTMyNThaFw0yNDA0MzAwOTMyNThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | DAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzENMAsGA1UECgwEV1NPMjENMAsG 7 | A1UECwwEV1NPMjESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAkdgncoCrz655Lq8pTdX07eoVBjdZDCUE6ueBd0D1hpJ0 9 | /zE3x3Az6tlvzs98PsPuGzaQOMmuLa4qxNJ+OKxJmutDUlClpuvxuf+jyq4gCV5t 10 | EIILWRMBjlBEpJfWm63+VKKU4nvBWNJ7KfhWjl8+DUdNSh2pCDLpUObmb9Kquqc1 11 | x4BgttjN4rx/P+3/v+1jETXzIP1L44yHtpQNv0khYf4j/aHjcEri9ykvpz1mtdac 12 | brKK25N4V1HHRwDqZiJzOCCISXDuqB6wguY/v4n0l1XtrEs7iCyfRFwNSKNrLqr2 13 | 3tR1CscmLfbH6ZLg5CYJTD+1uPSx0HMOB4Wv51PbWwIDAQABo2MwYTAUBgNVHREE 14 | DTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFH0KQ3YTZJxTsNsPyrZOSFgXXhG+MB0G 15 | A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjALBgNVHQ8EBAMCBPAwDQYJKoZI 16 | hvcNAQELBQADggEBAFNJ34CIiIlCxmyp27+KA224LaHVtL5DucFK0P22FQ+QKkON 17 | iUwO70KoVFreBH1Smxu4ePWk6rMZFOM5oL8HXYg3twy+5eGcL3PQd7X5dwAqlViv 18 | zokoi6SDaA/bIG6J/O1U9Qd4XEVJdVuLqjk1+cp70ALt0X6B7sNLfjFcbz3jQULN 19 | nK8HNvqbn7zQuP10s8p5y2qVkPBA/pjigRDsIWR6p78QESF+TaHFjxfcD6f9cnYi 20 | e+yEHERtG8k8x5jLFe+odI1/QGZP8Fy0oKT+E/TJ1FBh4rB1FtKylqGeauPu89Dn 21 | aJ9+kvpNQ94yFmEuhtDByvDijxAqvlin3TPIfy8= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /examples/order-management-service/app_backend/resources/sts-public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDqTCCApGgAwIBAgIEYfEVSjANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxDTALBgNVBAoM 4 | BFdTTzIxDTALBgNVBAsMBFdTTzIxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjAx 5 | MjYwOTMyNThaFw0yNDA0MzAwOTMyNThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | DAJDQTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzENMAsGA1UECgwEV1NPMjENMAsG 7 | A1UECwwEV1NPMjESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAkdgncoCrz655Lq8pTdX07eoVBjdZDCUE6ueBd0D1hpJ0 9 | /zE3x3Az6tlvzs98PsPuGzaQOMmuLa4qxNJ+OKxJmutDUlClpuvxuf+jyq4gCV5t 10 | EIILWRMBjlBEpJfWm63+VKKU4nvBWNJ7KfhWjl8+DUdNSh2pCDLpUObmb9Kquqc1 11 | x4BgttjN4rx/P+3/v+1jETXzIP1L44yHtpQNv0khYf4j/aHjcEri9ykvpz1mtdac 12 | brKK25N4V1HHRwDqZiJzOCCISXDuqB6wguY/v4n0l1XtrEs7iCyfRFwNSKNrLqr2 13 | 3tR1CscmLfbH6ZLg5CYJTD+1uPSx0HMOB4Wv51PbWwIDAQABo2MwYTAUBgNVHREE 14 | DTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFH0KQ3YTZJxTsNsPyrZOSFgXXhG+MB0G 15 | A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjALBgNVHQ8EBAMCBPAwDQYJKoZI 16 | hvcNAQELBQADggEBAFNJ34CIiIlCxmyp27+KA224LaHVtL5DucFK0P22FQ+QKkON 17 | iUwO70KoVFreBH1Smxu4ePWk6rMZFOM5oL8HXYg3twy+5eGcL3PQd7X5dwAqlViv 18 | zokoi6SDaA/bIG6J/O1U9Qd4XEVJdVuLqjk1+cp70ALt0X6B7sNLfjFcbz3jQULN 19 | nK8HNvqbn7zQuP10s8p5y2qVkPBA/pjigRDsIWR6p78QESF+TaHFjxfcD6f9cnYi 20 | e+yEHERtG8k8x5jLFe+odI1/QGZP8Fy0oKT+E/TJ1FBh4rB1FtKylqGeauPu89Dn 21 | aJ9+kvpNQ94yFmEuhtDByvDijxAqvlin3TPIfy8= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /.github/workflows/build-with-bal-test-graalvm.yml: -------------------------------------------------------------------------------- 1 | name: GraalVM Check 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | lang_tag: 7 | description: Branch/Release Tag of the Ballerina Lang 8 | required: true 9 | default: master 10 | lang_version: 11 | description: Ballerina Lang Version (If given ballerina lang buid will be skipped) 12 | required: false 13 | default: '' 14 | native_image_options: 15 | description: Default native-image options 16 | required: false 17 | default: '' 18 | schedule: 19 | - cron: '30 18 * * *' 20 | pull_request: 21 | branches: 22 | - master 23 | types: [opened, synchronize, reopened, labeled, unlabeled] 24 | 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} 27 | cancel-in-progress: true 28 | 29 | jobs: 30 | call_stdlib_workflow: 31 | name: Run StdLib Workflow 32 | if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} 33 | uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-template.yml@main 34 | with: 35 | lang_tag: ${{ inputs.lang_tag }} 36 | lang_version: ${{ inputs.lang_version }} 37 | native_image_options: ${{ inputs.native_image_options }} 38 | additional_windows_build_flags: '-Pdisable=skipOnWindows' 39 | -------------------------------------------------------------------------------- /.github/workflows/update_spec.yml: -------------------------------------------------------------------------------- 1 | name: Update Specifications 2 | 3 | env: 4 | SPEC_FOLDER_PATH: 'docs/spec' 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - master 11 | paths: 12 | - 'docs/spec/**' 13 | 14 | jobs: 15 | update_specs: 16 | name: Update Specifications 17 | if: github.repository_owner == 'ballerina-platform' 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout Repository 22 | uses: actions/checkout@v3 23 | 24 | - name: Get current date 25 | id: date 26 | run: echo "::set-output name=date::$(date +'%Y-%m-%d')" 27 | 28 | - name: Get Repo Name 29 | id: repo_name 30 | run: | 31 | MODULE=${{ github.event.repository.name }} 32 | echo "::set-output name=short_name::${MODULE##*-}" 33 | 34 | - name: Trigger Workflow 35 | run: | 36 | curl --request POST \ 37 | 'https://api.github.com/repos/ballerina-platform/ballerina-dev-website/dispatches' \ 38 | -H 'Accept: application/vnd.github.v3+json' \ 39 | -H 'Authorization: Bearer ${{ secrets.BALLERINA_BOT_TOKEN }}' \ 40 | --data "{ 41 | \"event_type\": \"update-stdlib-specs\", 42 | \"client_payload\": { 43 | \"module_name\": \"${{ github.event.repository.name }}\", 44 | \"short_name\": \"${{ steps.repo_name.outputs.short_name }}\", 45 | \"file_dir\": \"${{ github.event.repository.name }}/${{ env.SPEC_FOLDER_PATH }}\", 46 | \"release_date\": \"${{ steps.date.outputs.date }}\" 47 | } 48 | }" 49 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.3/userguide/multi_project_builds.html 8 | */ 9 | 10 | pluginManagement { 11 | plugins { 12 | id "com.github.spotbugs-base" version "${spotbugsPluginVersion}" 13 | id "com.github.johnrengelman.shadow" version "${shadowJarPluginVersion}" 14 | id "de.undercouch.download" version "${downloadPluginVersion}" 15 | id "net.researchgate.release" version "${releasePluginVersion}" 16 | id "io.ballerina.plugin" version "${ballerinaGradlePluginVersion}" 17 | } 18 | 19 | repositories { 20 | gradlePluginPortal() 21 | maven { 22 | url = 'https://maven.pkg.github.com/ballerina-platform/*' 23 | credentials { 24 | username System.getenv("packageUser") 25 | password System.getenv("packagePAT") 26 | } 27 | } 28 | } 29 | } 30 | 31 | plugins { 32 | id "com.gradle.enterprise" version "3.13.2" 33 | } 34 | 35 | rootProject.name = 'oauth2' 36 | 37 | include ':checkstyle' 38 | include ':oauth2-native' 39 | include ':oauth2-ballerina' 40 | 41 | project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") 42 | project(':oauth2-native').projectDir = file('native') 43 | project(':oauth2-ballerina').projectDir = file('ballerina') 44 | 45 | gradleEnterprise { 46 | buildScan { 47 | termsOfServiceUrl = 'https://gradle.com/terms-of-service' 48 | termsOfServiceAgree = 'yes' 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ballerina/tests/resources/key/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBXKLp9WJUuJko 3 | SfDn3HM2LWVxiHrP11me6D4X2AqoUiCZ1w976q1LuBlzWNhONYwJvHXewUuRmo0d 4 | 4NRp2ma0qjdMN246XWyozpzPfJdvovfiT+V6HnSbMg82ZppfF3e85Cgrg6mSA7Wf 5 | faaHr62SnexseZ9ioNCuRUrW2Zg208l99IItP4K4iGclQlyjs3S6drH7JJk3rv8B 6 | f76wmmoCEAhknOtGLq543dEUIjFgNdIy/GsSY52fiA4LSjUCv27kGn03nayqoZs3 7 | XHqM0qLdt9SPXbYe7l75j0o6Z9tyGFCKXY/zVwiLfRUqDIOfkN5YS4Ohit52Tj7q 8 | oBmvEzD7AgMBAAECggEAXM/F4u23OummmQ1T1kaIMpqnaalt06jCGAywYBMUsmca 9 | FMYDyfg5lVXkjKl1p8crTeD1AHjWawTjskgYnkmf3ocxXXF3mFBnIUX7o7HURLg7 10 | +RcxoUgwiRiFaZZ7szX3JoLbfzzbcHNQ37kavccBVWwQsFMiU3Tlw+LbKwK6/row 11 | LYsQPx7gT4u7hViat4vQDTYcgyjvvFCiek4ndL6O9K49MxIMU678UXB6ia5iUevy 12 | vgEfcYkKQ5EQ38qS3ZwsubPvj4633jvAJRr/hJD8XINZC74kTXeV3BGH2LlpQOEq 13 | kWkOypwYNjnXtt1JO8+Iu6mEXKUoiIBPfGrJ3vDSQQKBgQDmYPc7kfYan/LHjJRv 14 | iE2CwbC26yVA6+BEPQv9z7jChO9Q6cUbGvM8EEVNpC9nmFogkslzJhz55HP84QZL 15 | u3ptU+D96ncq6zkBqxBfRnZG++D36+XRXIwzz3h+g1Nwrl0y0MFbwlkMm3ZqJdd6 16 | pZz1FZGd6zvQftW8m7jPSKHuswKBgQCPv6czFOZR6bI+qCQdaORpe9JGoAduOD+4 17 | YKl96s0eiAKhkGhFCrMd6GJwWRkpNcfwB+J9sMahORbfvwiYanI56h7Vi30DFPRb 18 | m1m8dLkr6z+8bxMxKJaMXIIjy3UDamgDr7QHInNUih2iGvtB8QqZ0aobsB2XIxZg 19 | qESTMcpYmQKBgHSwSqneraQgvgz7FLhFdtUzHDoacr0mfGqz7R37F99XDAyUy+SF 20 | ywvyRdgkwGodjhEPqH/tnyGn6GP+6nxzknhL0xtppkCT8kT5C4rmmsQrknChCL/5 21 | u34GqUaTaDEb8FLrz/SVRRuQpvLvBey2dADjkuVFH//kLoig64P6iyLnAoGBAIlF 22 | g+2L78YZXVXoS1SqbjUtQUigWXgvzunLpQ/Rwb9+MsUGmgwUg6fz2s1eyGBKM3xM 23 | i0VsIsKjOezBCPxD6oDTyk4yvlbLE+7HE5KcBJikNmFD0RgIonu3e6+jA0MXweyD 24 | RW/qviflHRdInNgDzxPE3KVEMX26zAvRpGrMCWdBAoGAdQ5SvX+mAC3cKqoQ9Zal 25 | lSqWoyjfzP5EaVRG8dtoLxbznQGTTvtHXc65/MznX/L9qkWCS6Eb4HH5M3hFNY46 26 | LNIzGQLznE1odwv7H5B8c0/m3DrKTxbh8bYcrR1BW5/nKZNNW7k1O6OjEozvAajK 27 | JQdp3KBU9S8CmBjGrRpJ2qw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /examples/order-management-service/api_gateway/resources/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBXKLp9WJUuJko 3 | SfDn3HM2LWVxiHrP11me6D4X2AqoUiCZ1w976q1LuBlzWNhONYwJvHXewUuRmo0d 4 | 4NRp2ma0qjdMN246XWyozpzPfJdvovfiT+V6HnSbMg82ZppfF3e85Cgrg6mSA7Wf 5 | faaHr62SnexseZ9ioNCuRUrW2Zg208l99IItP4K4iGclQlyjs3S6drH7JJk3rv8B 6 | f76wmmoCEAhknOtGLq543dEUIjFgNdIy/GsSY52fiA4LSjUCv27kGn03nayqoZs3 7 | XHqM0qLdt9SPXbYe7l75j0o6Z9tyGFCKXY/zVwiLfRUqDIOfkN5YS4Ohit52Tj7q 8 | oBmvEzD7AgMBAAECggEAXM/F4u23OummmQ1T1kaIMpqnaalt06jCGAywYBMUsmca 9 | FMYDyfg5lVXkjKl1p8crTeD1AHjWawTjskgYnkmf3ocxXXF3mFBnIUX7o7HURLg7 10 | +RcxoUgwiRiFaZZ7szX3JoLbfzzbcHNQ37kavccBVWwQsFMiU3Tlw+LbKwK6/row 11 | LYsQPx7gT4u7hViat4vQDTYcgyjvvFCiek4ndL6O9K49MxIMU678UXB6ia5iUevy 12 | vgEfcYkKQ5EQ38qS3ZwsubPvj4633jvAJRr/hJD8XINZC74kTXeV3BGH2LlpQOEq 13 | kWkOypwYNjnXtt1JO8+Iu6mEXKUoiIBPfGrJ3vDSQQKBgQDmYPc7kfYan/LHjJRv 14 | iE2CwbC26yVA6+BEPQv9z7jChO9Q6cUbGvM8EEVNpC9nmFogkslzJhz55HP84QZL 15 | u3ptU+D96ncq6zkBqxBfRnZG++D36+XRXIwzz3h+g1Nwrl0y0MFbwlkMm3ZqJdd6 16 | pZz1FZGd6zvQftW8m7jPSKHuswKBgQCPv6czFOZR6bI+qCQdaORpe9JGoAduOD+4 17 | YKl96s0eiAKhkGhFCrMd6GJwWRkpNcfwB+J9sMahORbfvwiYanI56h7Vi30DFPRb 18 | m1m8dLkr6z+8bxMxKJaMXIIjy3UDamgDr7QHInNUih2iGvtB8QqZ0aobsB2XIxZg 19 | qESTMcpYmQKBgHSwSqneraQgvgz7FLhFdtUzHDoacr0mfGqz7R37F99XDAyUy+SF 20 | ywvyRdgkwGodjhEPqH/tnyGn6GP+6nxzknhL0xtppkCT8kT5C4rmmsQrknChCL/5 21 | u34GqUaTaDEb8FLrz/SVRRuQpvLvBey2dADjkuVFH//kLoig64P6iyLnAoGBAIlF 22 | g+2L78YZXVXoS1SqbjUtQUigWXgvzunLpQ/Rwb9+MsUGmgwUg6fz2s1eyGBKM3xM 23 | i0VsIsKjOezBCPxD6oDTyk4yvlbLE+7HE5KcBJikNmFD0RgIonu3e6+jA0MXweyD 24 | RW/qviflHRdInNgDzxPE3KVEMX26zAvRpGrMCWdBAoGAdQ5SvX+mAC3cKqoQ9Zal 25 | lSqWoyjfzP5EaVRG8dtoLxbznQGTTvtHXc65/MznX/L9qkWCS6Eb4HH5M3hFNY46 26 | LNIzGQLznE1odwv7H5B8c0/m3DrKTxbh8bYcrR1BW5/nKZNNW7k1O6OjEozvAajK 27 | JQdp3KBU9S8CmBjGrRpJ2qw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /examples/order-management-service/app_backend/resources/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBXKLp9WJUuJko 3 | SfDn3HM2LWVxiHrP11me6D4X2AqoUiCZ1w976q1LuBlzWNhONYwJvHXewUuRmo0d 4 | 4NRp2ma0qjdMN246XWyozpzPfJdvovfiT+V6HnSbMg82ZppfF3e85Cgrg6mSA7Wf 5 | faaHr62SnexseZ9ioNCuRUrW2Zg208l99IItP4K4iGclQlyjs3S6drH7JJk3rv8B 6 | f76wmmoCEAhknOtGLq543dEUIjFgNdIy/GsSY52fiA4LSjUCv27kGn03nayqoZs3 7 | XHqM0qLdt9SPXbYe7l75j0o6Z9tyGFCKXY/zVwiLfRUqDIOfkN5YS4Ohit52Tj7q 8 | oBmvEzD7AgMBAAECggEAXM/F4u23OummmQ1T1kaIMpqnaalt06jCGAywYBMUsmca 9 | FMYDyfg5lVXkjKl1p8crTeD1AHjWawTjskgYnkmf3ocxXXF3mFBnIUX7o7HURLg7 10 | +RcxoUgwiRiFaZZ7szX3JoLbfzzbcHNQ37kavccBVWwQsFMiU3Tlw+LbKwK6/row 11 | LYsQPx7gT4u7hViat4vQDTYcgyjvvFCiek4ndL6O9K49MxIMU678UXB6ia5iUevy 12 | vgEfcYkKQ5EQ38qS3ZwsubPvj4633jvAJRr/hJD8XINZC74kTXeV3BGH2LlpQOEq 13 | kWkOypwYNjnXtt1JO8+Iu6mEXKUoiIBPfGrJ3vDSQQKBgQDmYPc7kfYan/LHjJRv 14 | iE2CwbC26yVA6+BEPQv9z7jChO9Q6cUbGvM8EEVNpC9nmFogkslzJhz55HP84QZL 15 | u3ptU+D96ncq6zkBqxBfRnZG++D36+XRXIwzz3h+g1Nwrl0y0MFbwlkMm3ZqJdd6 16 | pZz1FZGd6zvQftW8m7jPSKHuswKBgQCPv6czFOZR6bI+qCQdaORpe9JGoAduOD+4 17 | YKl96s0eiAKhkGhFCrMd6GJwWRkpNcfwB+J9sMahORbfvwiYanI56h7Vi30DFPRb 18 | m1m8dLkr6z+8bxMxKJaMXIIjy3UDamgDr7QHInNUih2iGvtB8QqZ0aobsB2XIxZg 19 | qESTMcpYmQKBgHSwSqneraQgvgz7FLhFdtUzHDoacr0mfGqz7R37F99XDAyUy+SF 20 | ywvyRdgkwGodjhEPqH/tnyGn6GP+6nxzknhL0xtppkCT8kT5C4rmmsQrknChCL/5 21 | u34GqUaTaDEb8FLrz/SVRRuQpvLvBey2dADjkuVFH//kLoig64P6iyLnAoGBAIlF 22 | g+2L78YZXVXoS1SqbjUtQUigWXgvzunLpQ/Rwb9+MsUGmgwUg6fz2s1eyGBKM3xM 23 | i0VsIsKjOezBCPxD6oDTyk4yvlbLE+7HE5KcBJikNmFD0RgIonu3e6+jA0MXweyD 24 | RW/qviflHRdInNgDzxPE3KVEMX26zAvRpGrMCWdBAoGAdQ5SvX+mAC3cKqoQ9Zal 25 | lSqWoyjfzP5EaVRG8dtoLxbznQGTTvtHXc65/MznX/L9qkWCS6Eb4HH5M3hFNY46 26 | LNIzGQLznE1odwv7H5B8c0/m3DrKTxbh8bYcrR1BW5/nKZNNW7k1O6OjEozvAajK 27 | JQdp3KBU9S8CmBjGrRpJ2qw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/resources/api_gateway/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBXKLp9WJUuJko 3 | SfDn3HM2LWVxiHrP11me6D4X2AqoUiCZ1w976q1LuBlzWNhONYwJvHXewUuRmo0d 4 | 4NRp2ma0qjdMN246XWyozpzPfJdvovfiT+V6HnSbMg82ZppfF3e85Cgrg6mSA7Wf 5 | faaHr62SnexseZ9ioNCuRUrW2Zg208l99IItP4K4iGclQlyjs3S6drH7JJk3rv8B 6 | f76wmmoCEAhknOtGLq543dEUIjFgNdIy/GsSY52fiA4LSjUCv27kGn03nayqoZs3 7 | XHqM0qLdt9SPXbYe7l75j0o6Z9tyGFCKXY/zVwiLfRUqDIOfkN5YS4Ohit52Tj7q 8 | oBmvEzD7AgMBAAECggEAXM/F4u23OummmQ1T1kaIMpqnaalt06jCGAywYBMUsmca 9 | FMYDyfg5lVXkjKl1p8crTeD1AHjWawTjskgYnkmf3ocxXXF3mFBnIUX7o7HURLg7 10 | +RcxoUgwiRiFaZZ7szX3JoLbfzzbcHNQ37kavccBVWwQsFMiU3Tlw+LbKwK6/row 11 | LYsQPx7gT4u7hViat4vQDTYcgyjvvFCiek4ndL6O9K49MxIMU678UXB6ia5iUevy 12 | vgEfcYkKQ5EQ38qS3ZwsubPvj4633jvAJRr/hJD8XINZC74kTXeV3BGH2LlpQOEq 13 | kWkOypwYNjnXtt1JO8+Iu6mEXKUoiIBPfGrJ3vDSQQKBgQDmYPc7kfYan/LHjJRv 14 | iE2CwbC26yVA6+BEPQv9z7jChO9Q6cUbGvM8EEVNpC9nmFogkslzJhz55HP84QZL 15 | u3ptU+D96ncq6zkBqxBfRnZG++D36+XRXIwzz3h+g1Nwrl0y0MFbwlkMm3ZqJdd6 16 | pZz1FZGd6zvQftW8m7jPSKHuswKBgQCPv6czFOZR6bI+qCQdaORpe9JGoAduOD+4 17 | YKl96s0eiAKhkGhFCrMd6GJwWRkpNcfwB+J9sMahORbfvwiYanI56h7Vi30DFPRb 18 | m1m8dLkr6z+8bxMxKJaMXIIjy3UDamgDr7QHInNUih2iGvtB8QqZ0aobsB2XIxZg 19 | qESTMcpYmQKBgHSwSqneraQgvgz7FLhFdtUzHDoacr0mfGqz7R37F99XDAyUy+SF 20 | ywvyRdgkwGodjhEPqH/tnyGn6GP+6nxzknhL0xtppkCT8kT5C4rmmsQrknChCL/5 21 | u34GqUaTaDEb8FLrz/SVRRuQpvLvBey2dADjkuVFH//kLoig64P6iyLnAoGBAIlF 22 | g+2L78YZXVXoS1SqbjUtQUigWXgvzunLpQ/Rwb9+MsUGmgwUg6fz2s1eyGBKM3xM 23 | i0VsIsKjOezBCPxD6oDTyk4yvlbLE+7HE5KcBJikNmFD0RgIonu3e6+jA0MXweyD 24 | RW/qviflHRdInNgDzxPE3KVEMX26zAvRpGrMCWdBAoGAdQ5SvX+mAC3cKqoQ9Zal 25 | lSqWoyjfzP5EaVRG8dtoLxbznQGTTvtHXc65/MznX/L9qkWCS6Eb4HH5M3hFNY46 26 | LNIzGQLznE1odwv7H5B8c0/m3DrKTxbh8bYcrR1BW5/nKZNNW7k1O6OjEozvAajK 27 | JQdp3KBU9S8CmBjGrRpJ2qw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/resources/app_backend/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBXKLp9WJUuJko 3 | SfDn3HM2LWVxiHrP11me6D4X2AqoUiCZ1w976q1LuBlzWNhONYwJvHXewUuRmo0d 4 | 4NRp2ma0qjdMN246XWyozpzPfJdvovfiT+V6HnSbMg82ZppfF3e85Cgrg6mSA7Wf 5 | faaHr62SnexseZ9ioNCuRUrW2Zg208l99IItP4K4iGclQlyjs3S6drH7JJk3rv8B 6 | f76wmmoCEAhknOtGLq543dEUIjFgNdIy/GsSY52fiA4LSjUCv27kGn03nayqoZs3 7 | XHqM0qLdt9SPXbYe7l75j0o6Z9tyGFCKXY/zVwiLfRUqDIOfkN5YS4Ohit52Tj7q 8 | oBmvEzD7AgMBAAECggEAXM/F4u23OummmQ1T1kaIMpqnaalt06jCGAywYBMUsmca 9 | FMYDyfg5lVXkjKl1p8crTeD1AHjWawTjskgYnkmf3ocxXXF3mFBnIUX7o7HURLg7 10 | +RcxoUgwiRiFaZZ7szX3JoLbfzzbcHNQ37kavccBVWwQsFMiU3Tlw+LbKwK6/row 11 | LYsQPx7gT4u7hViat4vQDTYcgyjvvFCiek4ndL6O9K49MxIMU678UXB6ia5iUevy 12 | vgEfcYkKQ5EQ38qS3ZwsubPvj4633jvAJRr/hJD8XINZC74kTXeV3BGH2LlpQOEq 13 | kWkOypwYNjnXtt1JO8+Iu6mEXKUoiIBPfGrJ3vDSQQKBgQDmYPc7kfYan/LHjJRv 14 | iE2CwbC26yVA6+BEPQv9z7jChO9Q6cUbGvM8EEVNpC9nmFogkslzJhz55HP84QZL 15 | u3ptU+D96ncq6zkBqxBfRnZG++D36+XRXIwzz3h+g1Nwrl0y0MFbwlkMm3ZqJdd6 16 | pZz1FZGd6zvQftW8m7jPSKHuswKBgQCPv6czFOZR6bI+qCQdaORpe9JGoAduOD+4 17 | YKl96s0eiAKhkGhFCrMd6GJwWRkpNcfwB+J9sMahORbfvwiYanI56h7Vi30DFPRb 18 | m1m8dLkr6z+8bxMxKJaMXIIjy3UDamgDr7QHInNUih2iGvtB8QqZ0aobsB2XIxZg 19 | qESTMcpYmQKBgHSwSqneraQgvgz7FLhFdtUzHDoacr0mfGqz7R37F99XDAyUy+SF 20 | ywvyRdgkwGodjhEPqH/tnyGn6GP+6nxzknhL0xtppkCT8kT5C4rmmsQrknChCL/5 21 | u34GqUaTaDEb8FLrz/SVRRuQpvLvBey2dADjkuVFH//kLoig64P6iyLnAoGBAIlF 22 | g+2L78YZXVXoS1SqbjUtQUigWXgvzunLpQ/Rwb9+MsUGmgwUg6fz2s1eyGBKM3xM 23 | i0VsIsKjOezBCPxD6oDTyk4yvlbLE+7HE5KcBJikNmFD0RgIonu3e6+jA0MXweyD 24 | RW/qviflHRdInNgDzxPE3KVEMX26zAvRpGrMCWdBAoGAdQ5SvX+mAC3cKqoQ9Zal 25 | lSqWoyjfzP5EaVRG8dtoLxbznQGTTvtHXc65/MznX/L9qkWCS6Eb4HH5M3hFNY46 26 | LNIzGQLznE1odwv7H5B8c0/m3DrKTxbh8bYcrR1BW5/nKZNNW7k1O6OjEozvAajK 27 | JQdp3KBU9S8CmBjGrRpJ2qw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/app_backend.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/http; 18 | 19 | listener http:Listener appBackend = new (9090, 20 | secureSocket = { 21 | key: { 22 | certFile: "./resources/app_backend/public.crt", 23 | keyFile: "./resources/app_backend/private.key" 24 | } 25 | } 26 | ); 27 | 28 | final http:Client webClient = check new ("https://localhost:8080", 29 | secureSocket = { 30 | cert: "./resources/app_backend/public.crt" 31 | }, 32 | auth = { 33 | tokenUrl: "https://wso2is-sts-service:9443/oauth2/token", 34 | clientId: "uDMwA4hKR9H3deeXxvNf4sSU0i4a", 35 | clientSecret: "8FOUOKUQfOp47pUfJCsPA5X4clga", 36 | scopes: ["customer"], 37 | clientConfig: { 38 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 39 | secureSocket: { 40 | disable: true 41 | } 42 | } 43 | } 44 | ); 45 | 46 | isolated service /'order on appBackend { 47 | isolated resource function post .(@http:Payload json payload) returns json|error { 48 | return webClient->post("/order", payload); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ballerina/tests/resources/key/encryptedPrivate.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIE6jAcBgoqhkiG9w0BDAEEMA4ECJ1AVEOFT+s1AgIFDASCBMiHG70PvakbuVha 3 | 9JBcXWzje1B30LItVdpiETbu5LNG3v0IIX7DmRQCBJxXkkCeZ3Bcw/A1cjkGIR0j 4 | 6P1AyUAlZBU9+GcCF2BcpyaMOxNVQuHUZA9//fnlfO6KMTdIg9b/MutH++bLSoh9 5 | JkHivtgEQVEAu79NpTdKh9XklIWUBe5gU4OteBL0/feKgB6NFeYm7vNb8b486jSB 6 | oXWC7l8Y5VoU4grhDT9dL1PQqLQvIRWTnB1BhUE9OquWrRRaGE7WeWABzzFhHB4X 7 | DdHABqsNE9qeEGHrUBKfjfg6+8GIEjjJcpJt9wGBi4yHbUaao7xueu0aA9nVVo75 8 | 6PJOwcJqoPhwkDFpAYADLJTVxh8EqbaihSe3HVqjsF3au5nu338VbJu8VZ2rAgTv 9 | V2IxsV/BWrv8RIbt/EINz82uU0umbhpvUakMjZGD01NCvx6mshfKA3zIEac7apmc 10 | 6wwoqh42sEHk3i7H/xQhuS32ACzRDwXzXYf2f8CZVrSk7Ajc4w4mwY5YylCmn+P5 11 | YK6fRCaJAN2J4CGvIut3LvjGX8pNuu9msFMrFjbITL/xeUzIrJoE1C9O2V1dQ5LU 12 | O4wVFGKHkFpBBzxxDQwRbYn66EbL4/dCwtIkj7kncz7Y+qYBi+Voe81RSL1mBtR/ 13 | nOQPIHNOLSUvIPA2aj4ufcdSrbXapMfrTOiO+EUuBOGE6FncaGm3hfKnPa1C4fuZ 14 | yokvpEMNOUs5eJjSC0tcP+zibTkiMHy2vhvrI3xme6oIFG7Nvxi2MxcWolUNmXlk 15 | UVGyJJurL9QhKfjGSIuEZgJd7/PrDK0gzEQBMS/10BUPRe1pE+05GEOvJBbhw1Bt 16 | 6b3LjbVtNnR/2/G3I9LlwRLac4IHlU/JMy58E3Uxim0/rJJ7uGGbXsxdwFS4968I 17 | /ekA1PcZgvNet60rMYxSXz6QPwYnvM94gFD6I5PgUhJJPeQZiw9kFLBqKsUjBrjY 18 | jFAcTVW6sWEo1CMsUC94gwvHvpOLtPHjik/iPAfHXeGQ9baTaArMAJiQO+0h9O3Y 19 | 0rXM8gEz4zAID3N4neVjjMda7C/2avAfJRoBxYAFMcuMNTta4mMmXOCs+M6x45GI 20 | w/KPAZbsRTcRWLjU5QguqP6eAxSghHITruW0HEqscT20K3cRWXXDEOr8ZqId90xY 21 | PQo5ZEvaJLGgxN4Q7CUPJJ1mKHruTmMm+UOxIuXRR2eaqsJDnLovmu4jGOSLVYV4 22 | FFJ7PxD+tx9b8WS8tckS/Pjm2PcUAJz3I6/DFc3iKwoeCXBIG/djwWfabRcoGb0U 23 | Sq8tiqvCjT/LuubpQgKhkEnxWCBRR+Qns4quJJ2Kead/g8E19cUxolGPTW9jr2GF 24 | 1XWuGytK0PqUOIMsuJkhWN3dErFmftcfmfhqFFulOvE7eWr+ZToyFESmNK+MNnAL 25 | mmZFOt7zTNwooQkzpVohLtKPwe3k71dhhsK3gQBoscKQFnMQSmKLM7c4A6HPcq20 26 | XFtRM30O5cSO2jLZ+sAcJS81uIrcPWWf8yY5lMZ1irHFri6jnwcv7Sbe3rqZJFer 27 | 9ckVz4rq399UHOyujNT2gjIaFnEwmHfM0yaZ0dYG2JU/jXdbqsTLKsHSOZsrkkL5 28 | 9BaHO8wfzVx/CnQfP6M= 29 | -----END ENCRYPTED PRIVATE KEY----- 30 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/api_gateway.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | import ballerina/http; 19 | 20 | listener http:Listener apiGateway = new (8080, 21 | secureSocket = { 22 | key: { 23 | certFile: "./resources/api_gateway/public.crt", 24 | keyFile: "./resources/api_gateway/private.key" 25 | } 26 | } 27 | ); 28 | 29 | 30 | @http:ServiceConfig { 31 | auth: [ 32 | { 33 | oauth2IntrospectionConfig: { 34 | url: "https://wso2is-sts-service:9443/oauth2/introspect", 35 | clientConfig: { 36 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 37 | secureSocket: { 38 | disable: true 39 | } 40 | }, 41 | cacheConfig: { 42 | capacity: 10, 43 | evictionFactor: 0.25, 44 | evictionPolicy: cache:LRU, 45 | defaultMaxAge: -1 46 | } 47 | }, 48 | scopes: ["customer"] 49 | } 50 | ] 51 | } 52 | isolated service /'order on apiGateway { 53 | isolated resource function post .(@http:Payload json payload) returns json { 54 | return payload; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ballerina/tests/test_utils.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/test; 18 | 19 | const string KEYSTORE_PATH = "tests/resources/keystore/ballerinaKeystore.p12"; 20 | const string TRUSTSTORE_PATH = "tests/resources/keystore/ballerinaTruststore.p12"; 21 | const string PRIVATE_KEY_PATH = "tests/resources/key/private.key"; 22 | const string ENCRYPTED_PRIVATE_KEY_PATH = "tests/resources/key/encryptedPrivate.key"; 23 | const string PUBLIC_CERT_PATH = "tests/resources/cert/public.crt"; 24 | const string WSO2_PUBLIC_CERT_PATH = "tests/resources/cert/wso2Public.crt"; 25 | 26 | isolated function assertToken(string token) { 27 | string[] parts = re`-`.split(token); 28 | test:assertEquals(parts.length(), 5); 29 | test:assertEquals(parts[0].length(), 8); 30 | test:assertEquals(parts[1].length(), 4); 31 | test:assertEquals(parts[2].length(), 4); 32 | test:assertEquals(parts[3].length(), 4); 33 | test:assertEquals(parts[4].length(), 12); 34 | } 35 | 36 | // Build the complete error message by evaluating all the inner causes and asserting the inclusion. 37 | isolated function assertContains(error err, string text) { 38 | string message = err.message(); 39 | error? cause = err.cause(); 40 | while cause is error { 41 | message += " " + cause.message(); 42 | cause = cause.cause(); 43 | } 44 | test:assertTrue(message.includes(text)); 45 | } 46 | -------------------------------------------------------------------------------- /ballerina/init.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/jballerina.java; 18 | 19 | # Represents the default connection timeout values for the oauth2 client. 20 | public const decimal DEFAULT_CONNECT_TIMEOUT = 15; 21 | 22 | # Represents the default request timeout value for the oauth2 client. 23 | public const decimal DEFAULT_REQ_TIMEOUT = 30; 24 | 25 | # Represents the maximum time(in seconds) to wait for a connection to be established with the oauth2 endpoint. 26 | # Defaults to 15 seconds. This is a global configuration which will be applied to all the internal oauth2 client calls. 27 | public configurable decimal globalConnectTimeout = DEFAULT_CONNECT_TIMEOUT; 28 | 29 | # Represents the maximum time(in seconds) to wait for a response before the oauth2 endpoint request times out. 30 | # Defaults to 30 seconds. This is a global configuration which will be applied to all the internal oauth2 client calls. 31 | public configurable decimal globalReqTimeout = DEFAULT_REQ_TIMEOUT; 32 | 33 | isolated function init() returns error? { 34 | setModule(); 35 | check setOauth2ConnectionTimeout(globalConnectTimeout); 36 | check setOauth2RequestTimeout(globalReqTimeout); 37 | } 38 | 39 | isolated function setModule() = @java:Method { 40 | 'class: "io.ballerina.stdlib.oauth2.ModuleUtils" 41 | } external; 42 | 43 | isolated function setOauth2ConnectionTimeout(decimal timeout) returns error? = @java:Method { 44 | 'class: "io.ballerina.stdlib.oauth2.ModuleUtils" 45 | } external; 46 | 47 | isolated function setOauth2RequestTimeout(decimal timeout) returns error? = @java:Method { 48 | 'class: "io.ballerina.stdlib.oauth2.ModuleUtils" 49 | } external; 50 | -------------------------------------------------------------------------------- /native/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | plugins { 19 | id 'java' 20 | id 'checkstyle' 21 | id 'com.github.spotbugs' 22 | } 23 | 24 | description = 'Ballerina - OAuth2 Java Utils' 25 | 26 | dependencies { 27 | checkstyle project(':checkstyle') 28 | checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" 29 | 30 | implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" 31 | implementation group: 'io.ballerina.stdlib', name: 'crypto-native', version: "${stdlibCryptoVersion}" 32 | } 33 | 34 | checkstyle { 35 | toolVersion '7.8.2' 36 | configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") 37 | configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] 38 | } 39 | 40 | checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") 41 | 42 | spotbugsMain { 43 | def classLoader = plugins["com.github.spotbugs"].class.classLoader 44 | def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") 45 | def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") 46 | effort = SpotBugsEffort.MAX 47 | reportLevel = SpotBugsConfidence.LOW 48 | reportsDir = file("$project.buildDir/reports/spotbugs") 49 | reports { 50 | html.enabled true 51 | text.enabled = true 52 | } 53 | def excludeFile = file("${rootDir}/spotbugs-exclude.xml") 54 | if(excludeFile.exists()) { 55 | excludeFilter = excludeFile 56 | } 57 | } 58 | 59 | def excludePattern = '**/module-info.java' 60 | tasks.withType(Checkstyle) { 61 | exclude excludePattern 62 | } 63 | 64 | compileJava { 65 | doFirst { 66 | options.compilerArgs = [ 67 | '--module-path', classpath.asPath, 68 | ] 69 | classpath = files() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/order-management-service/api_gateway/api_gateway.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | import ballerina/http; 19 | 20 | listener http:Listener apiGateway = new (9090, 21 | secureSocket = { 22 | key: { 23 | certFile: "./resources/public.crt", 24 | keyFile: "./resources/private.key" 25 | } 26 | } 27 | ); 28 | 29 | 30 | @http:ServiceConfig { 31 | auth: [ 32 | { 33 | oauth2IntrospectionConfig: { 34 | url: "https://localhost:9443/oauth2/introspect", 35 | clientConfig: { 36 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 37 | secureSocket: { 38 | cert: "./resources/sts-public.crt" 39 | } 40 | }, 41 | cacheConfig: { 42 | capacity: 10, 43 | evictionFactor: 0.25, 44 | evictionPolicy: cache:LRU, 45 | defaultMaxAge: -1 46 | } 47 | }, 48 | scopes: ["customer"] 49 | } 50 | ] 51 | } 52 | service /'order on apiGateway { 53 | resource function get [string orderId]() returns json|error { 54 | // We need to call the 'Order Service' via mTLS. For this guide, since we are not interested of the security of 55 | // rest of the components, we will be returning a success mock response. 56 | return { 57 | id: "100500", 58 | name: "Sample order", 59 | items: [ 60 | { 61 | category: "electronics", 62 | code: "SOWH1000XM4", 63 | qty: 2 64 | }, 65 | { 66 | category: "books", 67 | code: "978-1617295959", 68 | qty: 1 69 | } 70 | ] 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /examples/order-management-service/app_backend/app_backend.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/http; 18 | 19 | configurable string clientId = ?; 20 | configurable string clientSecret = ?; 21 | 22 | listener http:Listener appBackend = new (8080, 23 | secureSocket = { 24 | key: { 25 | certFile: "./resources/public.crt", 26 | keyFile: "./resources/private.key" 27 | } 28 | } 29 | ); 30 | 31 | final http:Client webClient = check new ("https://localhost:9090", 32 | secureSocket = { 33 | cert: "./resources/public.crt" 34 | }, 35 | auth = { 36 | tokenUrl: "https://localhost:9443/oauth2/token", 37 | clientId: clientId, 38 | clientSecret: clientSecret, 39 | scopes: ["customer"], 40 | clientConfig: { 41 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 42 | secureSocket: { 43 | cert: "./resources/sts-public.crt" 44 | } 45 | } 46 | } 47 | ); 48 | 49 | final http:Client mobileClient = check new ("https://localhost:9090", 50 | secureSocket = { 51 | cert: "./resources/public.crt" 52 | } 53 | ); 54 | 55 | service /'order on appBackend { 56 | resource function get web(string orderId) returns json|error { 57 | return webClient->get("/order/" + orderId); 58 | } 59 | 60 | resource function get mobile(string orderId, string idToken) returns json|error { 61 | http:ClientOAuth2Handler handler = new({ 62 | tokenUrl: "https://localhost:9443/oauth2/token", 63 | assertion: idToken, 64 | clientId: clientId, 65 | clientSecret: clientSecret, 66 | scopes: ["customer"], 67 | clientConfig: { 68 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 69 | secureSocket: { 70 | cert: "./resources/sts-public.crt" 71 | } 72 | } 73 | }); 74 | return mobileClient->get("/order/" + orderId, check handler.getSecurityHeaders()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/oauth2/ModuleUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package io.ballerina.stdlib.oauth2; 20 | 21 | import io.ballerina.runtime.api.Environment; 22 | import io.ballerina.runtime.api.Module; 23 | import io.ballerina.runtime.api.creators.ErrorCreator; 24 | import io.ballerina.runtime.api.utils.StringUtils; 25 | import io.ballerina.runtime.api.values.BDecimal; 26 | 27 | /** 28 | * Utility functions relevant to module operations. 29 | * 30 | * @since 2.0.0 31 | */ 32 | public class ModuleUtils { 33 | 34 | private static Module oauth2Module; 35 | private static double oauth2ConnectionTimeout = 15.0; 36 | private static double oauth2RequestTimeout = 30.0; 37 | 38 | private ModuleUtils() {} 39 | 40 | public static void setModule(Environment env) { 41 | oauth2Module = env.getCurrentModule(); 42 | } 43 | 44 | public static Module getModule() { 45 | return oauth2Module; 46 | } 47 | 48 | public static Object setOauth2ConnectionTimeout(BDecimal timeout) { 49 | // Called in module init, failure will cause the program to fail 50 | oauth2ConnectionTimeout = timeout.floatValue(); 51 | if (oauth2ConnectionTimeout <= 0) { 52 | String errMsg = "OAuth2 connection timeout must be greater than zero"; 53 | return ErrorCreator.createError(ModuleUtils.getModule(), OAuth2Constants.OAUTH2_ERROR_TYPE, 54 | StringUtils.fromString(errMsg), null, null); 55 | } 56 | return null; 57 | } 58 | 59 | public static double getOauth2ConnectionTimeout() { 60 | return oauth2ConnectionTimeout; 61 | } 62 | 63 | public static Object setOauth2RequestTimeout(BDecimal timeout) { 64 | // Called in module init, failure will cause the program to fail 65 | oauth2RequestTimeout = timeout.floatValue(); 66 | if (oauth2RequestTimeout <= 0) { 67 | String errMsg = "OAuth2 request timeout must be greater than zero"; 68 | return ErrorCreator.createError(ModuleUtils.getModule(), OAuth2Constants.OAUTH2_ERROR_TYPE, 69 | StringUtils.fromString(errMsg), null, null); 70 | } 71 | return null; 72 | } 73 | 74 | public static double getOauth2RequestTimeout() { 75 | return oauth2RequestTimeout; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/oauth2/OAuth2Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package io.ballerina.stdlib.oauth2; 20 | 21 | import io.ballerina.runtime.api.utils.StringUtils; 22 | import io.ballerina.runtime.api.values.BString; 23 | 24 | /** 25 | * Constants related to Ballerina OAuth2 stdlib. 26 | */ 27 | public class OAuth2Constants { 28 | 29 | private OAuth2Constants() {} 30 | 31 | public static final String OAUTH2_ERROR_TYPE = "Error"; 32 | 33 | public static final String SINGLE_SLASH = "/"; 34 | public static final String DOUBLE_SLASH = "//"; 35 | public static final String SCHEME_SEPARATOR = "://"; 36 | public static final String HTTP_SCHEME = "http"; 37 | public static final String HTTPS_SCHEME = "https"; 38 | 39 | public static final BString HTTP_VERSION = StringUtils.fromString("httpVersion"); 40 | public static final BString SECURE_SOCKET = StringUtils.fromString("secureSocket"); 41 | public static final BString DISABLE = StringUtils.fromString("disable"); 42 | public static final BString CERT = StringUtils.fromString("cert"); 43 | public static final BString KEY = StringUtils.fromString("key"); 44 | public static final BString CERT_FILE = StringUtils.fromString("certFile"); 45 | public static final BString KEY_FILE = StringUtils.fromString("keyFile"); 46 | public static final BString KEY_PASSWORD = StringUtils.fromString("keyPassword"); 47 | public static final BString PATH = StringUtils.fromString("path"); 48 | public static final BString PASSWORD = StringUtils.fromString("password"); 49 | public static final BString CUSTOM_HEADERS = StringUtils.fromString("customHeaders"); 50 | public static final BString CUSTOM_PAYLOAD = StringUtils.fromString("customPayload"); 51 | public static final BString CONNECTION_TIMEOUT = StringUtils.fromString("connectTimeout"); 52 | public static final BString REQUEST_TIMEOUT = StringUtils.fromString("reqTimeout"); 53 | 54 | public static final String TLS = "TLS"; 55 | public static final String PKCS12 = "PKCS12"; 56 | public static final String HTTP_2 = "HTTP_2"; 57 | 58 | public static final String CONTENT_TYPE = "Content-Type"; 59 | public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded"; 60 | 61 | public static final String NATIVE_DATA_PUBLIC_KEY_CERTIFICATE = "NATIVE_DATA_PUBLIC_KEY_CERTIFICATE"; 62 | public static final String NATIVE_DATA_PRIVATE_KEY = "NATIVE_DATA_PRIVATE_KEY"; 63 | 64 | public static final String RUNTIME_WARNING_PREFIX = "warning: [ballerina/oauth2] "; 65 | public static final String HTTPS_RECOMMENDATION_ERROR = "HTTPS is recommended but using HTTP"; 66 | } 67 | -------------------------------------------------------------------------------- /ballerina/oauth2_commons.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/crypto; 18 | import ballerina/jballerina.java; 19 | 20 | # Represents the configurations of the client used to call the introspection endpoint. 21 | # 22 | # + httpVersion - The HTTP version of the client 23 | # + customHeaders - The list of custom HTTP headers 24 | # + customPayload - The list of custom HTTP payload parameters 25 | # + auth - The client auth configurations 26 | # + secureSocket - SSL/TLS-related configurations 27 | # + connectTimeout - Maximum time(in seconds) to wait for a connection to be established. By default, uses the global 28 | # configuration `globalConnectTimeout` which defaults to 15 seconds 29 | # + reqTimeout - Maximum time(in seconds) to wait for a response before the request times out. By default, uses the global 30 | # configuration `globalReqTimeout` which defaults to 30 seconds 31 | public type ClientConfiguration record {| 32 | HttpVersion httpVersion = HTTP_1_1; 33 | map customHeaders?; 34 | string customPayload?; 35 | ClientAuth auth?; 36 | SecureSocket secureSocket?; 37 | decimal connectTimeout?; 38 | decimal reqTimeout?; 39 | |}; 40 | 41 | # Represents the the authentication configuration types for the HTTP client used for token introspection. 42 | public type ClientAuth ClientCredentialsGrantConfig|PasswordGrantConfig|RefreshTokenGrantConfig; 43 | 44 | # Represents the HTTP versions. 45 | public enum HttpVersion { 46 | HTTP_1_1, 47 | HTTP_2 48 | } 49 | 50 | # Represents the SSL/TLS configurations. 51 | # 52 | # + disable - Disable SSL validation 53 | # + cert - Configurations associated with the `crypto:TrustStore` or single certificate file that the client trusts 54 | # + key - Configurations associated with the `crypto:KeyStore` or combination of certificate and private key of the client 55 | public type SecureSocket record {| 56 | boolean disable = false; 57 | crypto:TrustStore|string cert?; 58 | crypto:KeyStore|CertKey key?; 59 | |}; 60 | 61 | # Represents the combination of the certificate file path, private key file path, and private key password if encrypted. 62 | # 63 | # + certFile - A file containing the certificate 64 | # + keyFile - A file containing the private key 65 | # + keyPassword - Password of the private key (if encrypted) 66 | public type CertKey record {| 67 | string certFile; 68 | string keyFile; 69 | string keyPassword?; 70 | |}; 71 | 72 | # Represents the credential-bearing methods. 73 | public enum CredentialBearer { 74 | AUTH_HEADER_BEARER, 75 | POST_BODY_BEARER 76 | } 77 | 78 | isolated function doHttpRequest(string url, ClientConfiguration clientConfig, map headers, string payload) 79 | returns string|Error = @java:Method { 80 | 'class: "io.ballerina.stdlib.oauth2.OAuth2Client" 81 | } external; 82 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | This file contains all the notable changes done to the Ballerina OAuth2 package through the releases. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | ## [Unreleased] 7 | 8 | ### Added 9 | 10 | - [Expose connection timeout and request timeout for the internal HTTP client used to obtain token or introspect, with default values](https://github.com/ballerina-platform/ballerina-library/issues/8121) 11 | 12 | ## [2.13.0] - 2025-02-11 13 | 14 | - This version maintains the latest dependency versions. 15 | 16 | ## [2.12.0] - 2024-08-20 17 | 18 | - This version maintains the latest dependency versions. 19 | 20 | ## [2.11.0] - 2024-05-03 21 | 22 | - This version maintains the latest dependency versions. 23 | 24 | ## [2.10.0] - 2023-09-15 25 | 26 | - This version maintains the latest dependency versions. 27 | 28 | ## [2.9.0] - 2023-06-30 29 | 30 | - This version maintains the latest dependency versions. 31 | 32 | ## [2.8.0] - 2023-06-01 33 | 34 | - This version maintains the latest dependency versions. 35 | 36 | ## [2.7.0] - 2023-04-10 37 | 38 | - This version maintains compatibility with Lang Update 5 without any external changes. 39 | 40 | ## [2.6.1] - 2023-03-09 41 | 42 | ### Fixed 43 | - [OAuth2 client fails when a password with special characters provided](https://github.com/ballerina-platform/ballerina-standard-library/issues/4110) 44 | 45 | ## [2.6.0] - 2023-02-20 46 | 47 | ### Changed 48 | - [Allow password grant type to refresh token using the inferred values](https://github.com/ballerina-platform/ballerina-standard-library/issues/3879) 49 | - [Allow string value for scope field in client configurations](https://github.com/ballerina-platform/ballerina-standard-library/issues/3877) 50 | 51 | ## [2.5.0] - 2022-11-29 52 | 53 | ### Changed 54 | - [API docs updated](https://github.com/ballerina-platform/ballerina-standard-library/issues/3463) 55 | 56 | ### Fixed 57 | - [Oauth2 client treats 201-Created response as a failure](https://github.com/ballerina-platform/ballerina-standard-library/issues/3334) 58 | - [Java exception when tokenUrl set to empty string in OAuth2GrantConfig](https://github.com/ballerina-platform/ballerina-standard-library/issues/3402) 59 | 60 | ## [2.3.0] - 2022-04-30 61 | 62 | ### Changed 63 | - [Append the scheme of the HTTP client URL (token/introspection) based on the client configurations](https://github.com/ballerina-platform/ballerina-standard-library/issues/2816) 64 | 65 | ## [2.0.0] - 2021-10-10 66 | 67 | ### Added 68 | - [Add JWT bearer grant support for OAuth2](https://github.com/ballerina-platform/ballerina-standard-library/issues/1716) 69 | 70 | ## [1.1.0-beta.1] - 2021-05-06 71 | 72 | ### Changed 73 | - [Refactor OAuth2 client implementation with grant types](https://github.com/ballerina-platform/ballerina-standard-library/issues/1206) 74 | 75 | ### Fixed 76 | - [Improve the logic of extracting refresh_token from the token endpoint response](https://github.com/ballerina-platform/ballerina-standard-library/issues/1206) 77 | 78 | ## [1.1.0-alpha8] - 2021-04-22 79 | 80 | ### Changed 81 | - [Improve error messages and log messages](https://github.com/ballerina-platform/ballerina-standard-library/issues/1242) 82 | 83 | ## [1.1.0-alpha6] - 2021-04-02 84 | 85 | ### Changed 86 | - Remove usages of `checkpanic` for type narrowing 87 | 88 | ### Security 89 | - [Update log messages for a security concern](https://github.com/ballerina-platform/ballerina-standard-library/issues/1203) 90 | 91 | ## [1.1.0-alpha5] - 2021-03-19 92 | 93 | ### Added 94 | - [Add OAuth2 client auth support for introspection request](https://github.com/ballerina-platform/ballerina-standard-library/issues/935) 95 | - [Add cert file and mTLS support for JDK11 client](https://github.com/ballerina-platform/ballerina-standard-library/issues/936) 96 | 97 | ### Changed 98 | - Refactor error messages and debug logs 99 | - Update error types and log API 100 | - Update for Time API changes 101 | - Generate OAuth2 token while the provider is initialized 102 | - Update for Cache API changes 103 | - Update for refresh token grant config 104 | -------------------------------------------------------------------------------- /load-tests/order_management_service/scripts/http-post-request.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | continue 17 | 18 | false 19 | -1 20 | 21 | ${__P(users)} 22 | ${__P(rampUpPeriod,60)} 23 | true 24 | ${__P(duration)} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ${__P(payload)} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ${__P(host,localhost)} 42 | ${__P(port,9090)} 43 | ${__P(protocol,http)} 44 | 45 | ${__P(path)} 46 | POST 47 | true 48 | false 49 | true 50 | false 51 | 52 | HttpClient4 53 | 10000 54 | 120000 55 | 56 | 57 | 58 | 59 | 60 | Content-Type 61 | application/json 62 | 63 | 64 | 65 | 66 | 67 | 68 | ${__P(response_size)} 69 | 70 | 71 | Assertion.response_data 72 | false 73 | 16 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ballerina OAuth2 Library 2 | =================== 3 | 4 | [![Build](https://github.com/ballerina-platform/module-ballerina-oauth2/actions/workflows/build-timestamped-master.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerina-oauth2/actions/workflows/build-timestamped-master.yml) 5 | [![codecov](https://codecov.io/gh/ballerina-platform/module-ballerina-oauth2/branch/master/graph/badge.svg)](https://codecov.io/gh/ballerina-platform/module-ballerina-oauth2) 6 | [![Trivy](https://github.com/ballerina-platform/module-ballerina-oauth2/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerina-oauth2/actions/workflows/trivy-scan.yml) 7 | [![GraalVM Check](https://github.com/ballerina-platform/module-ballerina-oauth2/actions/workflows/build-with-bal-test-graalvm.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerina-oauth2/actions/workflows/build-with-bal-test-graalvm.yml) 8 | [![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerina-oauth2.svg?label=Last%20Commit)](https://github.com/ballerina-platform/module-ballerina-oauth2/commits/master) 9 | [![GitHub issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-standard-library/module/oauth2.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-standard-library/labels/module%2Foauth2) 10 | 11 | 12 | This library provides a framework for interacting with OAuth2 authorization servers as specified in the [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) and [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662). 13 | 14 | The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service or by allowing the third-party application to obtain access on its own behalf. 15 | 16 | The Ballerina `oauth2` library facilitates auth providers that are to be used by the clients and listeners of different protocol connectors. 17 | 18 | ### Listener OAuth2 provider 19 | 20 | Represents the listener OAuth2 provider, which is used to validate the received credential (access token) by calling the configured OAuth2 introspection endpoint. 21 | 22 | ### Client OAuth2 provider 23 | 24 | Represents the client OAuth2 provider, which is used to generate OAuth2 access tokens using the configured OAuth2 token endpoint configurations. This supports the client credentials grant type, password grant type, and refresh token grant type. 25 | 26 | ## Issues and projects 27 | 28 | Issues and Projects tabs are disabled for this repository as this is part of the Ballerina Standard Library. To report bugs, request new features, start new discussions, view project boards, etc., go to the [Ballerina Standard Library parent repository](https://github.com/ballerina-platform/ballerina-standard-library). 29 | 30 | This repository only contains the source code for the module. 31 | 32 | ## Build from the source 33 | 34 | ### Set up the prerequisites 35 | 36 | 1. Download and install Java SE Development Kit (JDK) version 21 (from one of the following locations). 37 | 38 | * [Oracle](https://www.oracle.com/java/technologies/downloads/) 39 | 40 | * [OpenJDK](https://adoptium.net) 41 | 42 | > **Note:** Set the `JAVA_HOME` environment variable to the path name of the directory into which you installed JDK. 43 | 44 | 2. Export your GitHub Personal Access Token (PAT) with the 'read package' permission as follows: 45 | 46 | ``` 47 | export packageUser= 48 | export packagePAT= 49 | ``` 50 | 51 | 3. Download and install [Docker](https://www.docker.com/). 52 | 53 | ### Build the source 54 | 55 | Execute the commands below to build from the source. 56 | 57 | 1. To build the package: 58 | ``` 59 | ./gradlew clean build 60 | ``` 61 | 2. To run the tests: 62 | ``` 63 | ./gradlew clean test 64 | ``` 65 | 66 | 3. To run a group of tests 67 | ``` 68 | ./gradlew clean test -Pgroups= 69 | ``` 70 | 71 | 4. To build the without the tests: 72 | ``` 73 | ./gradlew clean build -x test 74 | ``` 75 | 76 | 5. To debug package implementation: 77 | ``` 78 | ./gradlew clean build -Pdebug= 79 | ``` 80 | 81 | 6. To debug with Ballerina language: 82 | ``` 83 | ./gradlew clean build -PbalJavaDebug= 84 | ``` 85 | 86 | 7. Publish the generated artifacts to the local Ballerina central repository: 87 | ``` 88 | ./gradlew clean build -PpublishToLocalCentral=true 89 | ``` 90 | 91 | 8. Publish the generated artifacts to the Ballerina central repository: 92 | ``` 93 | ./gradlew clean build -PpublishToCentral=true 94 | ``` 95 | 96 | ## Contribute to Ballerina 97 | 98 | As an open source project, Ballerina welcomes contributions from the community. 99 | 100 | For more information, go to the [contribution guidelines](https://github.com/ballerina-platform/ballerina-lang/blob/master/CONTRIBUTING.md). 101 | 102 | ## Code of conduct 103 | 104 | All contributors are encouraged to read the [Ballerina Code of Conduct](https://ballerina.io/code-of-conduct). 105 | 106 | ## Useful links 107 | 108 | * For more information go to the [`oauth2` library](https://lib.ballerina.io/ballerina/oauth2/latest). 109 | * For example demonstrations of the usage, go to [Ballerina By Examples](https://ballerina.io/learn/by-example/). 110 | * Chat live with us via our [Discord server](https://discord.gg/ballerinalang). 111 | * Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. 112 | -------------------------------------------------------------------------------- /ballerina/Dependencies.toml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED FILE. DO NOT MODIFY. 2 | 3 | # This file is auto-generated by Ballerina for managing dependency versions. 4 | # It should not be modified by hand. 5 | 6 | [ballerina] 7 | dependencies-toml-version = "2" 8 | distribution-version = "2201.13.0-20251015-051600-27035519" 9 | 10 | [[package]] 11 | org = "ballerina" 12 | name = "cache" 13 | version = "3.10.0" 14 | dependencies = [ 15 | {org = "ballerina", name = "constraint"}, 16 | {org = "ballerina", name = "jballerina.java"}, 17 | {org = "ballerina", name = "task"}, 18 | {org = "ballerina", name = "time"} 19 | ] 20 | modules = [ 21 | {org = "ballerina", packageName = "cache", moduleName = "cache"} 22 | ] 23 | 24 | [[package]] 25 | org = "ballerina" 26 | name = "constraint" 27 | version = "1.7.0" 28 | dependencies = [ 29 | {org = "ballerina", name = "jballerina.java"} 30 | ] 31 | 32 | [[package]] 33 | org = "ballerina" 34 | name = "crypto" 35 | version = "2.9.2" 36 | dependencies = [ 37 | {org = "ballerina", name = "jballerina.java"}, 38 | {org = "ballerina", name = "time"} 39 | ] 40 | modules = [ 41 | {org = "ballerina", packageName = "crypto", moduleName = "crypto"} 42 | ] 43 | 44 | [[package]] 45 | org = "ballerina" 46 | name = "io" 47 | version = "1.8.0" 48 | dependencies = [ 49 | {org = "ballerina", name = "jballerina.java"}, 50 | {org = "ballerina", name = "lang.value"} 51 | ] 52 | 53 | [[package]] 54 | org = "ballerina" 55 | name = "jballerina.java" 56 | version = "0.0.0" 57 | modules = [ 58 | {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} 59 | ] 60 | 61 | [[package]] 62 | org = "ballerina" 63 | name = "lang.__internal" 64 | version = "0.0.0" 65 | dependencies = [ 66 | {org = "ballerina", name = "jballerina.java"}, 67 | {org = "ballerina", name = "lang.object"} 68 | ] 69 | 70 | [[package]] 71 | org = "ballerina" 72 | name = "lang.array" 73 | version = "0.0.0" 74 | scope = "testOnly" 75 | dependencies = [ 76 | {org = "ballerina", name = "jballerina.java"}, 77 | {org = "ballerina", name = "lang.__internal"} 78 | ] 79 | 80 | [[package]] 81 | org = "ballerina" 82 | name = "lang.error" 83 | version = "0.0.0" 84 | scope = "testOnly" 85 | dependencies = [ 86 | {org = "ballerina", name = "jballerina.java"} 87 | ] 88 | 89 | [[package]] 90 | org = "ballerina" 91 | name = "lang.int" 92 | version = "0.0.0" 93 | dependencies = [ 94 | {org = "ballerina", name = "jballerina.java"}, 95 | {org = "ballerina", name = "lang.__internal"}, 96 | {org = "ballerina", name = "lang.object"} 97 | ] 98 | 99 | [[package]] 100 | org = "ballerina" 101 | name = "lang.object" 102 | version = "0.0.0" 103 | 104 | [[package]] 105 | org = "ballerina" 106 | name = "lang.runtime" 107 | version = "0.0.0" 108 | scope = "testOnly" 109 | dependencies = [ 110 | {org = "ballerina", name = "jballerina.java"} 111 | ] 112 | modules = [ 113 | {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} 114 | ] 115 | 116 | [[package]] 117 | org = "ballerina" 118 | name = "lang.value" 119 | version = "0.0.0" 120 | dependencies = [ 121 | {org = "ballerina", name = "jballerina.java"} 122 | ] 123 | 124 | [[package]] 125 | org = "ballerina" 126 | name = "log" 127 | version = "2.13.0" 128 | dependencies = [ 129 | {org = "ballerina", name = "io"}, 130 | {org = "ballerina", name = "jballerina.java"}, 131 | {org = "ballerina", name = "lang.value"}, 132 | {org = "ballerina", name = "observe"} 133 | ] 134 | modules = [ 135 | {org = "ballerina", packageName = "log", moduleName = "log"} 136 | ] 137 | 138 | [[package]] 139 | org = "ballerina" 140 | name = "oauth2" 141 | version = "2.15.0" 142 | dependencies = [ 143 | {org = "ballerina", name = "cache"}, 144 | {org = "ballerina", name = "crypto"}, 145 | {org = "ballerina", name = "jballerina.java"}, 146 | {org = "ballerina", name = "lang.runtime"}, 147 | {org = "ballerina", name = "log"}, 148 | {org = "ballerina", name = "test"}, 149 | {org = "ballerina", name = "time"}, 150 | {org = "ballerina", name = "url"} 151 | ] 152 | modules = [ 153 | {org = "ballerina", packageName = "oauth2", moduleName = "oauth2"} 154 | ] 155 | 156 | [[package]] 157 | org = "ballerina" 158 | name = "observe" 159 | version = "1.5.1" 160 | dependencies = [ 161 | {org = "ballerina", name = "jballerina.java"} 162 | ] 163 | 164 | [[package]] 165 | org = "ballerina" 166 | name = "task" 167 | version = "2.11.0" 168 | dependencies = [ 169 | {org = "ballerina", name = "jballerina.java"}, 170 | {org = "ballerina", name = "time"}, 171 | {org = "ballerina", name = "uuid"} 172 | ] 173 | 174 | [[package]] 175 | org = "ballerina" 176 | name = "test" 177 | version = "0.0.0" 178 | scope = "testOnly" 179 | dependencies = [ 180 | {org = "ballerina", name = "jballerina.java"}, 181 | {org = "ballerina", name = "lang.array"}, 182 | {org = "ballerina", name = "lang.error"} 183 | ] 184 | modules = [ 185 | {org = "ballerina", packageName = "test", moduleName = "test"} 186 | ] 187 | 188 | [[package]] 189 | org = "ballerina" 190 | name = "time" 191 | version = "2.7.0" 192 | dependencies = [ 193 | {org = "ballerina", name = "jballerina.java"} 194 | ] 195 | modules = [ 196 | {org = "ballerina", packageName = "time", moduleName = "time"} 197 | ] 198 | 199 | [[package]] 200 | org = "ballerina" 201 | name = "url" 202 | version = "2.6.0" 203 | dependencies = [ 204 | {org = "ballerina", name = "jballerina.java"} 205 | ] 206 | modules = [ 207 | {org = "ballerina", packageName = "url", moduleName = "url"} 208 | ] 209 | 210 | [[package]] 211 | org = "ballerina" 212 | name = "uuid" 213 | version = "1.10.0" 214 | dependencies = [ 215 | {org = "ballerina", name = "crypto"}, 216 | {org = "ballerina", name = "jballerina.java"}, 217 | {org = "ballerina", name = "lang.int"}, 218 | {org = "ballerina", name = "time"} 219 | ] 220 | 221 | -------------------------------------------------------------------------------- /ballerina/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import org.apache.tools.ant.taskdefs.condition.Os 19 | 20 | plugins { 21 | id 'io.ballerina.plugin' 22 | } 23 | 24 | description = 'Ballerina - OAuth2 Ballerina' 25 | 26 | def packageName = "oauth2" 27 | def packageOrg = "ballerina" 28 | def tomlVersion = stripBallerinaExtensionVersion("${project.version}") 29 | def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") 30 | def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") 31 | 32 | def stripBallerinaExtensionVersion(String extVersion) { 33 | if (extVersion.matches(project.ext.timestampedVersionRegex)) { 34 | def splitVersion = extVersion.split('-') 35 | if (splitVersion.length > 3) { 36 | def strippedValues = splitVersion[0..-4] 37 | return strippedValues.join('-') 38 | } else { 39 | return extVersion 40 | } 41 | } else { 42 | return extVersion.replace("${project.ext.snapshotVersion}", "") 43 | } 44 | } 45 | 46 | ballerina { 47 | packageOrganization = packageOrg 48 | module = packageName 49 | langVersion = ballerinaLangVersion 50 | } 51 | 52 | task updateTomlFiles { 53 | doLast { 54 | def newBallerinaToml = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) 55 | newBallerinaToml = newBallerinaToml.replace("@toml.version@", tomlVersion) 56 | ballerinaTomlFile.text = newBallerinaToml 57 | } 58 | } 59 | 60 | task commitTomlFiles { 61 | doLast { 62 | project.exec { 63 | ignoreExitValue true 64 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 65 | commandLine 'cmd', '/c', "git commit -m \"[Automated] Update the native jar versions\" Ballerina.toml Dependencies.toml" 66 | } else { 67 | commandLine 'sh', '-c', "git commit -m '[Automated] Update the native jar versions' Ballerina.toml Dependencies.toml" 68 | } 69 | } 70 | } 71 | } 72 | 73 | publishing { 74 | publications { 75 | maven(MavenPublication) { 76 | artifact source: createArtifactZip, extension: 'zip' 77 | } 78 | } 79 | repositories { 80 | maven { 81 | name = "GitHubPackages" 82 | url = uri("https://maven.pkg.github.com/ballerina-platform/module-${packageOrg}-${packageName}") 83 | credentials { 84 | username = System.getenv("publishUser") 85 | password = System.getenv("publishPAT") 86 | } 87 | } 88 | } 89 | } 90 | 91 | task startWso2IS() { 92 | doLast { 93 | // This check is added to prevent starting the server in Windows OS, since the Docker image does not support 94 | // for Windows OS. 95 | if (!Os.isFamily(Os.FAMILY_WINDOWS)) { 96 | def stdOut = new ByteArrayOutputStream() 97 | exec { 98 | commandLine 'sh', '-c', "docker ps --filter name=wso2-is" 99 | standardOutput = stdOut 100 | } 101 | if (!stdOut.toString().contains("wso2-is")) { 102 | println "Starting WSO2 IS." 103 | exec { 104 | commandLine 'sh', '-c', "docker run --rm -d -p 9443:9443 --name wso2-is ldclakmal/wso2is-sts:latest" 105 | standardOutput = stdOut 106 | } 107 | println stdOut.toString() 108 | println "Waiting 100s until the WSO2 IS get initiated." 109 | sleep(100 * 1000) 110 | } else { 111 | println "WSO2 IS is already started." 112 | } 113 | } 114 | } 115 | } 116 | 117 | task stopWso2IS() { 118 | doLast { 119 | // This check is added to prevent trying to stop the server in Windows OS, since the Docker image not started 120 | // in Windows OS. 121 | if (!Os.isFamily(Os.FAMILY_WINDOWS)) { 122 | def stdOut = new ByteArrayOutputStream() 123 | exec { 124 | commandLine 'sh', '-c', "docker ps --filter name=wso2-is" 125 | standardOutput = stdOut 126 | } 127 | if (stdOut.toString().contains("wso2-is")) { 128 | println "Stopping WSO2 IS." 129 | exec { 130 | commandLine 'sh', '-c', "docker stop wso2-is" 131 | standardOutput = stdOut 132 | } 133 | println stdOut.toString() 134 | println "Waiting 15s until the WSO2 IS get stopped." 135 | sleep(15 * 1000) 136 | } else { 137 | println "WSO2 IS is not started." 138 | } 139 | } 140 | } 141 | } 142 | 143 | task startBallerinaSTS() { 144 | doLast { 145 | // This check is added to prevent starting the server in Windows OS, since the Docker image does not support 146 | // for Windows OS. 147 | if (!Os.isFamily(Os.FAMILY_WINDOWS)) { 148 | def stdOut = new ByteArrayOutputStream() 149 | exec { 150 | commandLine 'sh', '-c', "docker ps --filter name=ballerina-sts" 151 | standardOutput = stdOut 152 | } 153 | if (!stdOut.toString().contains("ballerina-sts")) { 154 | println "Starting Ballerina STS." 155 | exec { 156 | // Mock STS: https://hub.docker.com/r/sabthar/ballerina-sts 157 | commandLine 'sh', '-c', "docker run --rm -d -p 9445:9445 -p 9444:9444 --name ballerina-sts sabthar/ballerina-sts:latest" 158 | standardOutput = stdOut 159 | } 160 | println stdOut.toString() 161 | println "Waiting 30s until the Ballerina STS get initiated." 162 | sleep(30 * 1000) 163 | } else { 164 | println "Ballerina STS is already started." 165 | } 166 | } 167 | } 168 | } 169 | 170 | task stopBallerinaSTS() { 171 | doLast { 172 | // This check is added to prevent trying to stop the server in Windows OS, since the Docker image not started 173 | // in Windows OS. 174 | if (!Os.isFamily(Os.FAMILY_WINDOWS)) { 175 | def stdOut = new ByteArrayOutputStream() 176 | exec { 177 | commandLine 'sh', '-c', "docker ps --filter name=ballerina-sts" 178 | standardOutput = stdOut 179 | } 180 | if (stdOut.toString().contains("ballerina-sts")) { 181 | println "Stopping Ballerina STS." 182 | exec { 183 | commandLine 'sh', '-c', "docker stop ballerina-sts" 184 | standardOutput = stdOut 185 | } 186 | println stdOut.toString() 187 | println "Waiting 15s until the Ballerina STS get stopped." 188 | sleep(15 * 1000) 189 | } else { 190 | println "Ballerina STS is not started." 191 | } 192 | } 193 | } 194 | } 195 | 196 | updateTomlFiles.dependsOn copyStdlibs 197 | 198 | test.finalizedBy stopWso2IS 199 | test.finalizedBy stopBallerinaSTS 200 | test.dependsOn startWso2IS 201 | test.dependsOn startBallerinaSTS 202 | test.dependsOn ":${packageName}-native:build" 203 | 204 | build.dependsOn "generatePomFileForMavenPublication" 205 | if (!project.gradle.startParameter.excludedTaskNames.contains('test')) { 206 | build.finalizedBy stopWso2IS 207 | build.finalizedBy stopBallerinaSTS 208 | build.dependsOn startWso2IS 209 | build.dependsOn startBallerinaSTS 210 | } 211 | build.dependsOn ":${packageName}-native:build" 212 | 213 | publishToMavenLocal.dependsOn build 214 | publish.dependsOn build 215 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/order-management-service/A Guideline on Securing Ballerina REST APIs.md: -------------------------------------------------------------------------------- 1 | # Secured Order Management Service with OAuth2 2 | 3 | [![Star on Github](https://img.shields.io/badge/-Star%20on%20Github-blue?style=social&logo=github)](https://github.com/ballerina-platform/module-ballerina-oauth2) 4 | 5 | _Authors_: @ldclakmal \ 6 | _Reviewers_: @shafreenAnfar \ 7 | _Created_: 2022/09/03 \ 8 | _Updated_: 2023/04/24 9 | 10 | ## Overview 11 | 12 | This guide explains how to secure an 'Order Management Service' (RESTful service) with OAuth2 using Ballerina. 13 | The following figure illustrates a high-level design diagram of the complete use case. 14 | 15 | ![Securing Microservice with OAuth2](./order-management-service.png) 16 | 17 | The end-user (customer), in this example, Alice and Bob, interacts with the system using the web/mobile app provided. 18 | This web/mobile app acts as a 'Client' on behalf of the user’s actions and calls to the 'API Gateway'. The 'API Gateway' 19 | routes the requests to 'Order Service', which is responsible for processing the order for the customer. The 'Inventory 20 | Service' is called by the 'Order Service' to process the inventory-related operations. 21 | 22 | **NOTE**: For this guide, since we are discussing the OAuth2 security aspects, we are focussing on the network 23 | interactions until the 'API Gateway' validates a request. 24 | 25 | Scenario 1 - Web App: 26 | - The end-user 'Alice' wants to retrieve her order details using the web app. 27 | - First, she signs in to the application with her credentials (username and password). 28 | - Now the web app needs to invoke an API on behalf of the logged-in end-user 'Alice'. It talks to the 'OAuth 29 | Authorization Server', trusted by the API, and uses the client credentials grant type, to get an access token. 30 | - Now the web app invokes the API with the access token. 31 | - The 'API Gateway' intercepts the request from the end-user, extracts the token, and then talks to the 'OAuth 32 | Authorization Server' connected to validate the token. 33 | 34 | Scenario 2 - Mobile App: 35 | - The end-user 'Bob' wants to retrieve her order details using the web app. 36 | - The end-user 'Bob' sign-in to the mobile app using the single sign-on (SSO) feature provided. 37 | - The end-user gets redirected to the OpenID Connect (OIDC) server and authenticates against the user-store or 38 | active-directory connected to it. After the authentication, the end-user gets redirected back to the mobile app, with 39 | an authorization code (assuming that we are using OAuth 2.0 authorization code grant type). 40 | - The mobile app talks directly to the OpenID Connect (OIDC) server and exchanges the authorization code from the 41 | previous step to an ID token, and an access token. The ID token itself is a JWT, which is signed by the OpenID Connect 42 | (OIDC) server. 43 | - Now the mobile app needs to invoke an API on behalf of the logged-in end-user 'Bob'. It talks to the 'OAuth 44 | Authorization Server', trusted by the API, and using the JWT grant type, exchanges the received JWT (ID token) to an 45 | access token. The 'OAuth Authorization Server' validates the JWT and makes sure that it’s being signed by a trusted 46 | identity provider. In this case, the 'OAuth Authorization Server' trusts the OpenID Connect (OIDC) identity provider. 47 | - Now the mobile app invokes the API with the access token. 48 | - The 'API Gateway' intercepts the request from the end-user, extracts the token, and then talks to the 'OAuth 49 | Authorization Server' connected to validate the token. 50 | 51 | ## Implementation 52 | 53 | **NOTE**: To get started with the implementation we need to have the web/mobile app to be ready. But, since we are 54 | discussing the OAuth2 security aspects by this guide, the frontend implementation of the web/mobile app is not 55 | explained. We will be using Ballerina as the app backend of both web/mobile app (hereinafter referred to as 'Web Client 56 | Backend' and 'Mobile Client Backend'). 57 | 58 | - Now, we can get started with the 'Web Client Backend', which is responsible to invoke an API on behalf of the 59 | logged-in end-user. It talks to the 'OAuth Authorization Server', trusted by the API, and uses the client credentials 60 | grant type, to get an access token. 61 | 62 | - The client authentication is enabled by setting the `auth` client configuration of the Ballerina HTTP client. It is 63 | configured with the client credentials grant configurations so that the Ballerina HTTP client knows how to get the 64 | access token and proceed with the actual API call. 65 | Refer to [`examples/order-management-service/app_backend`](./app_backend) for the implementation. 66 | 67 | - Now, we can get started with the 'Mobile Client Backend', which is also responsible to invoke an API on behalf of 68 | the logged-in end-user. It talks to the 'OAuth Authorization Server', trusted by the API, and using the JWT grant 69 | type, exchanges the received JWT (ID token) to an access token. The 'OAuth Authorization Server' validates the JWT 70 | and makes sure that it’s being signed by a trusted identity provider. In this case, the 'OAuth Authorization Server' 71 | trusts the OpenID Connect (OIDC) identity provider. 72 | 73 | - The client authentication is enabled by setting the `auth` client configuration of the Ballerina HTTP client. It is 74 | configured with the JWT bearer grant configurations so that the Ballerina HTTP client knows how to get the access 75 | token and proceed with the actual API call. 76 | Refer to [`examples/order-management-service/app_backend`](./app_backend) for the implementation. 77 | 78 | - Now, we can get started with the 'API Gateway', which is responsible to authorize the requests using OAuth2 and 79 | forward the request to the actual microservice via mTLS (mutual TLS). In this scenario, it is 'Order Service'. The 80 | 'API Gateway' service is secured by setting the `auth` attribute of `http:ServiceConfig` with the OAuth2 introspection 81 | configurations, so that the Ballerina HTTP service knows how to validate the access token with the configured 'OAuth 82 | Authorization Server'. Once validated, the business logic defined inside the resource will get executed. In this case, 83 | it will call the 'Order Service' via mTLS and return the response to the 'Client'. 84 | 85 | **NOTE**: For the simplicity of the article, since we are interested only in OAuth2 security aspects the rest of the 86 | components like 'Order Service' and 'Inventory Service' is not implemented. But, to complete the story, we 87 | will be returning a successful mock response from the 'API Gateway'. 88 | 89 | ## Testing 90 | 91 | We can run the 'API Gateway', 'Web Client Backend' and 'Mobile Client Backend' that we developed above, in our local 92 | environment. To complete the design diagram illustrated above, we have to run the 93 | ['OAuth Authorization Server'](https://hub.docker.com/repository/docker/ldclakmal/wso2is-sts) first. 94 | Open the terminal and execute the following command. This will take few seconds to start and run. 95 | ```shell 96 | $ docker run -d -p 9443:9443 ldclakmal/wso2is-sts:latest 97 | ``` 98 | 99 | Now, navigate to [`examples/order-management-service/api_gateway`](./api_gateway) directory and execute the following command. 100 | ```shell 101 | $ bal run 102 | ``` 103 | 104 | The successful execution of the service should show us the following output. 105 | ```shell 106 | Compiling source 107 | oauth2/api_gateway:1.0.0 108 | 109 | Running executable 110 | ``` 111 | 112 | Now, navigate to [`examples/order-management-service/app_backend`](./app_backend) directory and execute the same command. 113 | The successful execution of the service should show us the following output. 114 | ```shell 115 | Compiling source 116 | oauth2/app_backend:1.0.0 117 | 118 | Running executable 119 | ``` 120 | 121 | Now, we can test authentication and authorization checks being enforced on different actions by sending HTTP requests. 122 | For example, we have used the CURL commands to test each scenario as follows. 123 | 124 | ### Scenario 1 - Web App 125 | 126 | ```shell 127 | curl -k -v "https://localhost:8080/order/web?orderId=100500" 128 | ``` 129 | 130 | Output: 131 | ```shell 132 | < HTTP/1.1 200 OK 133 | < content-type: application/json 134 | < content-length: 163 135 | < server: ballerina 136 | < date: Fri, 3 Sep 2021 10:35:37 +0530 137 | 138 | {"id":"100500", "name":"Sample order", "items":[{"category":"electronics", "code":"SOWH1000XM4", "qty":2}, {"category":"books", "code":"978-1617295959", "qty":1}]} 139 | ``` 140 | 141 | ### Scenario 2 - Mobile App 142 | 143 | Here, we need to export our ID Token, that the mobile client received for the OpenID Connect (OIDC) response from the 144 | 'OAuth Authorization Server'. 145 | Refer to [WSO2 Identity Server Documentation - OpenID Connect](https://is.docs.wso2.com/en/latest/learn/openid-connect/) 146 | for more information. 147 | ```shell 148 | export ID_TOKEN="eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QiLCAia2lkIjoiTXpZeE1tRmtPR1l3TVdJMFpXTm1ORGN4TkdZd1ltTTRaVEEzTVdJMk5EQXpaR1F6TkdNMFpHIn0.eyJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo5NDQzL29hdXRoMi90b2tlbiIsICJzdWIiOiJhZG1pbiIsICJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo5NDQzL29hdXRoMi90b2tlbiIsICJleHAiOjE5NDQ0NzI2MjksICJuYmYiOjE2MjkxMTI2MjksICJpYXQiOjE2MjkxMTI2Mjl9.Qbi5kElPZlyViUUuYW9Ik4nXSeTIroacEDs4BoI0rAGAOBXfyWLW4Yxm6hAlb4GXtkPZ4YMO8c0mUgdXgvPVFqFYJuINNPu6Y_nExahAVD0VxCYRE59lEjRv7t_gqn5OxSu_jTGcgcHH8_j-tvL_-AHaqgflr5UljbTPtnQyXtLaPNeu3r7FoWs-LrewMPIm1aw5qc2gI2iYwI1jfIdpNlEjU6r_Mg6ou2D2AGqJa0QYN1FMqi4YJt2jHr60tQMQIWJ7zhKU4ShZESxYOVKK_cBOeL6K-A07pNEZYaSxtCU3609MIZ8EOUJuQUJb7zHHxG4QziHM8eBwFo26yovBFw"; 149 | 150 | curl -k -v "https://localhost:8080/order/mobile?orderId=100500&idToken=$ID_TOKEN" 151 | ``` 152 | 153 | Output: 154 | ```shell 155 | < HTTP/1.1 200 OK 156 | < content-type: application/json 157 | < content-length: 163 158 | < server: ballerina 159 | < date: Fri, 3 Sep 2021 10:35:37 +0530 160 | 161 | {"id":"100500", "name":"Sample order", "items":[{"category":"electronics", "code":"SOWH1000XM4", "qty":2}, {"category":"books", "code":"978-1617295959", "qty":1}]} 162 | ``` 163 | 164 | ## Deployment 165 | 166 | Once we are done with the development, we can deploy the service using any of the methods that are listed below. 167 | 168 | ### Deploying Locally 169 | 170 | As the first step, we have to run the 'OAuth Authorization Server' first. Open the terminal and execute the following 171 | command. This will take few seconds to start and run. 172 | ```shell 173 | $ docker run -d -p 9443:9443 ldclakmal/wso2is-sts:latest 174 | ``` 175 | 176 | Now, we can build Ballerina executable files (.jar) of the components that we developed above. Open the terminal and 177 | navigate to [`examples/order-management-service/api_gateway`](./api_gateway), and 178 | [`examples/order-management-service/app_backend`](./app_backend) directories, and execute the following command for 179 | each of them. 180 | ```shell 181 | $ bal build 182 | ``` 183 | 184 | The successful execution of the above command should show us the following outputs in order. 185 | ```shell 186 | Compiling source 187 | oauth2/api_gateway:1.0.0 188 | 189 | Generating executable 190 | target/bin/api_gateway.jar 191 | ``` 192 | 193 | ```shell 194 | Compiling source 195 | oauth2/app_backend:1.0.0 196 | 197 | Generating executable 198 | target/bin/app_backend.jar 199 | ``` 200 | 201 | Once the `*.jar` files are created inside the `target/bin` directories, we can run the components with the following 202 | commands in order. 203 | ```shell 204 | $ bal run target/bin/api_gateway.jar 205 | $ bal run target/bin/app_backend.jar 206 | ``` 207 | 208 | ### Deploying Code to Cloud 209 | 210 | Ballerina code to cloud supports generating the deployment artifacts of the Docker and Kubernetes. 211 | Refer to [Code to Cloud](https://ballerina.io/learn/user-guide/deployment/code-to-cloud/) guide for more information. 212 | 213 | ## Observability 214 | 215 | HTTP/HTTPS based Ballerina services and any client connectors are observable by default. 216 | [Observing Ballerina Code](https://ballerina.io/learn/user-guide/observability/observing-ballerina-code/) guide provides 217 | information on enabling Ballerina service observability with some of its supported systems. 218 | -------------------------------------------------------------------------------- /ballerina/listener_oauth2_provider.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | import ballerina/log; 19 | import ballerina/time; 20 | 21 | # Represents the introspection endpoint configurations. 22 | # 23 | # + url - URL of the introspection endpoint 24 | # + tokenTypeHint - A hint about the type of the token submitted for introspection 25 | # + optionalParams - Map of the optional parameters used for the introspection endpoint 26 | # + cacheConfig - Configurations for the cache used to store the OAuth2 access token and other related information 27 | # + defaultTokenExpTime - Expiration time (in seconds) of the tokens if the introspection response does not contain an `exp` field 28 | # + clientConfig - HTTP client configurations, which call the introspection endpoint 29 | public type IntrospectionConfig record { 30 | string url; 31 | string tokenTypeHint?; 32 | map optionalParams?; 33 | cache:CacheConfig cacheConfig?; 34 | decimal defaultTokenExpTime = 3600; 35 | ClientConfiguration clientConfig = {}; 36 | }; 37 | 38 | # Represents the introspection endpoint response. 39 | # 40 | # + active - Boolean indicator of whether or not the presented token is currently active 41 | # + scope - A JSON string containing a space-separated list of scopes associated with this token 42 | # + clientId - Client identifier for the OAuth 2.0 client, which requested this token 43 | # + username - Resource owner who authorized this token 44 | # + tokenType - Type of the token 45 | # + exp - Expiry time (seconds since the Epoch) 46 | # + iat - Time when the token was issued originally (seconds since the Epoch) 47 | # + nbf - Token is not to be used before this time (seconds since the Epoch) 48 | # + sub - Subject of the token 49 | # + aud - Intended audience of the token 50 | # + iss - Issuer of the token 51 | # + jti - String identifier for the token 52 | public type IntrospectionResponse record { 53 | boolean active; 54 | string scope?; 55 | string clientId?; 56 | string username?; 57 | string tokenType?; 58 | int exp?; 59 | int iat?; 60 | int nbf?; 61 | string sub?; 62 | string aud?; 63 | string iss?; 64 | string jti?; 65 | }; 66 | 67 | // IntrospectionResponse parameters 68 | const string ACTIVE = "active"; 69 | const string SCOPE = "scope"; 70 | const string CLIENT_ID = "client_id"; 71 | const string USERNAME = "username"; 72 | const string TOKEN_TYPE = "token_type"; 73 | const string EXP = "exp"; 74 | const string IAT = "iat"; 75 | const string NBF = "nbf"; 76 | const string SUB = "sub"; 77 | const string AUD = "aud"; 78 | const string ISS = "iss"; 79 | const string JTI = "jti"; 80 | 81 | # Represents the listener OAuth2 provider, which is used to validate the received credential (access token) by 82 | # calling the configured introspection endpoint. 83 | # ```ballerina 84 | # oauth2:IntrospectionConfig config = { 85 | # url: "https://localhost:9196/oauth2/token/introspect" 86 | # }; 87 | # oauth2:ListenerOAuth2Provider provider = new(config); 88 | # ``` 89 | public isolated class ListenerOAuth2Provider { 90 | 91 | private final IntrospectionConfig & readonly introspectionConfig; 92 | private final cache:Cache? oauth2Cache; 93 | private final ClientOAuth2Provider? clientOAuth2Provider; 94 | 95 | # Provides authorization based on the provided introspection configurations. 96 | # 97 | # + introspectionConfig - Introspection endpoint configurations 98 | public isolated function init(IntrospectionConfig introspectionConfig) { 99 | self.introspectionConfig = introspectionConfig.cloneReadOnly(); 100 | cache:CacheConfig? oauth2CacheConfig = introspectionConfig?.cacheConfig; 101 | if oauth2CacheConfig is cache:CacheConfig { 102 | self.oauth2Cache = new(oauth2CacheConfig); 103 | } else { 104 | self.oauth2Cache = (); 105 | } 106 | ClientAuth? auth = introspectionConfig.clientConfig?.auth; 107 | if auth is ClientAuth { 108 | self.clientOAuth2Provider = new(auth); 109 | } else { 110 | self.clientOAuth2Provider = (); 111 | } 112 | } 113 | 114 | # Validates the provided OAuth2 acess token against the introspection endpoint. 115 | # ```ballerina 116 | # boolean result = check provider.authorize(""); 117 | # ``` 118 | # 119 | # + credential - OAuth2 access token to be validated 120 | # + optionalParams - Map of the optional parameters used for the introspection endpoint 121 | # + return - An `oauth2:IntrospectionResponse` if the validation is successful or else an `oauth2:Error` if an error occurred 122 | public isolated function authorize(string credential, map? optionalParams = ()) returns IntrospectionResponse|Error { 123 | if credential == "" { 124 | return prepareError("Credential cannot be empty."); 125 | } 126 | 127 | cache:Cache? oauth2Cache = self.oauth2Cache; 128 | if oauth2Cache is cache:Cache && oauth2Cache.hasKey(credential) { 129 | IntrospectionResponse? response = validateFromCache(oauth2Cache, credential); 130 | if response is IntrospectionResponse { 131 | return response; 132 | } 133 | } 134 | IntrospectionResponse|Error validationResult = validate(credential, self.introspectionConfig, 135 | self.clientOAuth2Provider, optionalParams); 136 | if validationResult is IntrospectionResponse { 137 | if oauth2Cache is cache:Cache { 138 | addToCache(oauth2Cache, credential, validationResult, self.introspectionConfig.defaultTokenExpTime); 139 | } 140 | return validationResult; 141 | } 142 | return prepareError("OAuth2 validation failed.", validationResult); 143 | } 144 | } 145 | 146 | // Validates the provided OAuth2 access token by calling the introspection endpoint. 147 | isolated function validate(string token, IntrospectionConfig config, ClientOAuth2Provider? clientOAuth2Provider, 148 | map? optionalParams) returns IntrospectionResponse|Error { 149 | // Builds the request to be sent to the introspection endpoint. For more information, see the 150 | // [OAuth 2.0 Token Introspection RFC](https://tools.ietf.org/html/rfc7662#section-2.1) 151 | string textPayload = "token=" + token; 152 | string? tokenTypeHint = config?.tokenTypeHint; 153 | if tokenTypeHint is string { 154 | textPayload += "&token_type_hint=" + tokenTypeHint; 155 | } 156 | map? configOptionalParams = config?.optionalParams; 157 | if configOptionalParams is map { 158 | foreach [string, string] [key, value] in configOptionalParams.entries() { 159 | textPayload = textPayload + "&" + key.trim() + "=" + value.trim(); 160 | } 161 | } 162 | if optionalParams is map { 163 | foreach [string, string] [key, value] in optionalParams.entries() { 164 | textPayload = textPayload + "&" + key.trim() + "=" + value.trim(); 165 | } 166 | } 167 | map customHeadersMap = {}; 168 | ClientOAuth2Provider? oauth2Provider = clientOAuth2Provider; 169 | if oauth2Provider is ClientOAuth2Provider { 170 | string|Error accessToken = oauth2Provider.generateToken(); 171 | if accessToken is string { 172 | customHeadersMap["Authorization"] = "Bearer " + accessToken; 173 | } 174 | } 175 | string|Error stringResponse = doHttpRequest(config.url, config.clientConfig, customHeadersMap, textPayload); 176 | if stringResponse is string { 177 | json|error jsonResponse = stringResponse.fromJsonString(); 178 | if jsonResponse is json { 179 | return prepareIntrospectionResponse(jsonResponse); 180 | } 181 | return prepareError("Failed to convert '" + stringResponse + "' to JSON.", jsonResponse); 182 | } 183 | return prepareError("Failed to call the introspection endpoint '" + config.url + "'.", stringResponse); 184 | } 185 | 186 | isolated function prepareIntrospectionResponse(json payload) returns IntrospectionResponse { 187 | IntrospectionResponse introspectionResponse = { 188 | active: false 189 | }; 190 | map payloadMap = >payload; 191 | string[] keys = payloadMap.keys(); 192 | foreach string key in keys { 193 | match key { 194 | ACTIVE => { 195 | introspectionResponse.active = payloadMap[key]; 196 | } 197 | SCOPE => { 198 | introspectionResponse.scope = payloadMap[key]; 199 | } 200 | CLIENT_ID => { 201 | introspectionResponse.clientId = payloadMap[key]; 202 | } 203 | USERNAME => { 204 | introspectionResponse.username = payloadMap[key]; 205 | } 206 | TOKEN_TYPE => { 207 | introspectionResponse.tokenType = payloadMap[key]; 208 | } 209 | EXP => { 210 | introspectionResponse.exp = parseExpClaim(payloadMap[key]); 211 | } 212 | IAT => { 213 | introspectionResponse.iat = payloadMap[key]; 214 | } 215 | NBF => { 216 | introspectionResponse.nbf = payloadMap[key]; 217 | } 218 | SUB => { 219 | introspectionResponse.sub = payloadMap[key]; 220 | } 221 | AUD => { 222 | introspectionResponse.aud = payloadMap[key]; 223 | } 224 | ISS => { 225 | introspectionResponse.iss = payloadMap[key]; 226 | } 227 | JTI => { 228 | introspectionResponse.jti = payloadMap[key]; 229 | } 230 | _ => { 231 | introspectionResponse[key] = payloadMap[key]; 232 | } 233 | } 234 | } 235 | return introspectionResponse; 236 | } 237 | 238 | isolated function addToCache(cache:Cache oauth2Cache, string token, IntrospectionResponse response, 239 | decimal defaultTokenExpTime) { 240 | cache:Error? result; 241 | if response?.exp is int { 242 | result = oauth2Cache.put(token, response); 243 | } else { 244 | // If the `exp` parameter is not set by the introspection response, use the cache default expiry by 245 | // the `defaultTokenExpTime`. Then, the cached value will be removed when retrieving. 246 | result = oauth2Cache.put(token, response, defaultTokenExpTime); 247 | } 248 | if result is cache:Error { 249 | log:printDebug("Failed to add OAuth2 access token to the cache.", 'error = result); 250 | } 251 | } 252 | 253 | isolated function validateFromCache(cache:Cache oauth2Cache, string token) returns IntrospectionResponse? { 254 | any|cache:Error cachedEntry = oauth2Cache.get(token); 255 | if cachedEntry is () { 256 | // If the cached value is expired (defaultTokenExpTime is passed), it will return `()`. 257 | return; 258 | } 259 | if cachedEntry is any { 260 | IntrospectionResponse response = cachedEntry; 261 | int? expTime = response?.exp; 262 | // The `expTime` can be `()`. This means that the `defaultTokenExpTime` is not exceeded yet. 263 | // Hence, the token is still valid. If the `expTime` is provided in int, convert this to the current time and 264 | // check if the expiry time is exceeded. 265 | [int, decimal] currentTime = time:utcNow(); 266 | if expTime is () || expTime > currentTime[0] { 267 | return response; 268 | } 269 | cache:Error? result = oauth2Cache.invalidate(token); 270 | if result is cache:Error { 271 | log:printDebug("Failed to invalidate OAuth2 access token from the cache.", 'error = result); 272 | } 273 | } else { 274 | log:printDebug("Failed to validate the token from the cache.", 'error = cachedEntry); 275 | } 276 | return; 277 | } 278 | 279 | isolated function parseExpClaim(json expClaim) returns int|() { 280 | if expClaim is string { 281 | return checkpanic int:fromString(expClaim); 282 | } 283 | return expClaim; 284 | } 285 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/oauth2/OAuth2Client.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package io.ballerina.stdlib.oauth2; 20 | 21 | import io.ballerina.runtime.api.Environment; 22 | import io.ballerina.runtime.api.creators.ErrorCreator; 23 | import io.ballerina.runtime.api.utils.StringUtils; 24 | import io.ballerina.runtime.api.values.BDecimal; 25 | import io.ballerina.runtime.api.values.BError; 26 | import io.ballerina.runtime.api.values.BMap; 27 | import io.ballerina.runtime.api.values.BString; 28 | import io.ballerina.stdlib.crypto.nativeimpl.Decode; 29 | 30 | import java.io.FileInputStream; 31 | import java.io.IOException; 32 | import java.net.URI; 33 | import java.net.http.HttpClient; 34 | import java.net.http.HttpRequest; 35 | import java.net.http.HttpResponse; 36 | import java.security.KeyStore; 37 | import java.security.PrivateKey; 38 | import java.security.SecureRandom; 39 | import java.security.cert.X509Certificate; 40 | import java.time.Duration; 41 | import java.util.ArrayList; 42 | import java.util.Map; 43 | import java.util.UUID; 44 | 45 | import javax.net.ssl.KeyManager; 46 | import javax.net.ssl.KeyManagerFactory; 47 | import javax.net.ssl.SSLContext; 48 | import javax.net.ssl.TrustManager; 49 | import javax.net.ssl.TrustManagerFactory; 50 | import javax.net.ssl.X509TrustManager; 51 | 52 | import static java.lang.System.err; 53 | 54 | /** 55 | * Extern function to call identity provider endpoints like token endpoint, introspection endpoint, using the 56 | * JDK11 HttpClient and return the payload of the HTTP response. 57 | */ 58 | public class OAuth2Client { 59 | 60 | private OAuth2Client() {} 61 | 62 | public static Object doHttpRequest(Environment env, BString url, BMap clientConfig, 63 | BMap headers, BString payload) { 64 | BString customPayload = getBStringValueIfPresent(clientConfig, OAuth2Constants.CUSTOM_PAYLOAD); 65 | String textPayload = payload.getValue(); 66 | if (customPayload != null) { 67 | textPayload += "&" + customPayload; 68 | } 69 | 70 | ArrayList headersList = new ArrayList<>(); 71 | for (Map.Entry entry : headers.entrySet()) { 72 | headersList.add(entry.getKey().getValue()); 73 | headersList.add(entry.getValue().getValue()); 74 | } 75 | 76 | BMap customHeaders = getBMapValueIfPresent(clientConfig, OAuth2Constants.CUSTOM_HEADERS); 77 | if (customHeaders != null) { 78 | for (Map.Entry entry : customHeaders.entrySet()) { 79 | headersList.add(entry.getKey().getValue()); 80 | headersList.add(((BString) entry.getValue()).getValue()); 81 | } 82 | } 83 | 84 | String httpVersion = getBStringValueIfPresent(clientConfig, OAuth2Constants.HTTP_VERSION).getValue(); 85 | BMap secureSocket = getBMapValueIfPresent(clientConfig, OAuth2Constants.SECURE_SOCKET); 86 | 87 | int connectionTimeout; 88 | int requestTimeout; 89 | try { 90 | connectionTimeout = getConnectionTimeoutInMillis(clientConfig); 91 | requestTimeout = getRequestTimeoutInMillis(clientConfig); 92 | } catch (BError e) { 93 | return e; 94 | } 95 | 96 | HttpRequest request; 97 | URI uri; 98 | try { 99 | uri = buildUri(url.getValue(), secureSocket); 100 | } catch (IllegalArgumentException e) { 101 | return createError("Failed to create URI for the provided value \"" + url + "\"."); 102 | } 103 | 104 | if (headersList.isEmpty()) { 105 | request = buildHttpRequest(uri, textPayload, requestTimeout); 106 | } else { 107 | String[] flatHeaders = headersList.toArray(String[]::new); 108 | request = buildHttpRequest(uri, flatHeaders, textPayload, requestTimeout); 109 | } 110 | 111 | if (secureSocket != null) { 112 | try { 113 | SSLContext sslContext = getSslContext(secureSocket); 114 | HttpClient client = buildHttpClient(httpVersion, sslContext, connectionTimeout); 115 | return callEndpoint(env, client, request); 116 | } catch (Exception e) { 117 | return createError("Failed to init SSL context. " + e.getMessage()); 118 | } 119 | } 120 | HttpClient client = buildHttpClient(httpVersion, connectionTimeout); 121 | return callEndpoint(env, client, request); 122 | } 123 | 124 | private static URI buildUri(String url, BMap secureSocket) throws IllegalArgumentException { 125 | String[] urlParts = url.split(OAuth2Constants.SCHEME_SEPARATOR, 2); 126 | if (urlParts.length == 1) { 127 | urlParts = secureSocket != null ? new String[]{OAuth2Constants.HTTPS_SCHEME, urlParts[0]} : 128 | new String[]{OAuth2Constants.HTTP_SCHEME, urlParts[0]}; 129 | } else { 130 | if (urlParts[0].equals(OAuth2Constants.HTTP_SCHEME) && secureSocket != null) { 131 | err.println(OAuth2Constants.RUNTIME_WARNING_PREFIX + OAuth2Constants.HTTPS_RECOMMENDATION_ERROR); 132 | } 133 | } 134 | urlParts[1] = urlParts[1].replaceAll(OAuth2Constants.DOUBLE_SLASH, OAuth2Constants.SINGLE_SLASH); 135 | url = urlParts[0] + OAuth2Constants.SCHEME_SEPARATOR + urlParts[1]; 136 | return URI.create(url); 137 | } 138 | 139 | private static SSLContext getSslContext(BMap secureSocket) throws Exception { 140 | boolean disable = secureSocket.getBooleanValue(OAuth2Constants.DISABLE); 141 | if (disable) { 142 | return initSslContext(); 143 | } 144 | BMap key = (BMap) getBMapValueIfPresent(secureSocket, OAuth2Constants.KEY); 145 | Object cert = secureSocket.get(OAuth2Constants.CERT); 146 | if (cert == null) { 147 | throw new Exception("Need to configure 'crypto:TrustStore' or 'cert' with client SSL certificates file."); 148 | } 149 | KeyManagerFactory kmf; 150 | TrustManagerFactory tmf; 151 | if (cert instanceof BString) { 152 | if (key != null) { 153 | if (key.containsKey(OAuth2Constants.CERT_FILE)) { 154 | BString certFile = key.get(OAuth2Constants.CERT_FILE); 155 | BString keyFile = key.get(OAuth2Constants.KEY_FILE); 156 | BString keyPassword = getBStringValueIfPresent(key, OAuth2Constants.KEY_PASSWORD); 157 | kmf = getKeyManagerFactory(certFile, keyFile, keyPassword); 158 | } else { 159 | kmf = getKeyManagerFactory(key); 160 | } 161 | tmf = getTrustManagerFactory((BString) cert); 162 | return buildSslContext(kmf.getKeyManagers(), tmf.getTrustManagers()); 163 | } else { 164 | tmf = getTrustManagerFactory((BString) cert); 165 | return buildSslContext(null, tmf.getTrustManagers()); 166 | } 167 | } 168 | if (cert instanceof BMap) { 169 | BMap trustStore = (BMap) cert; 170 | if (key != null) { 171 | if (key.containsKey(OAuth2Constants.CERT_FILE)) { 172 | BString certFile = key.get(OAuth2Constants.CERT_FILE); 173 | BString keyFile = key.get(OAuth2Constants.KEY_FILE); 174 | BString keyPassword = getBStringValueIfPresent(key, OAuth2Constants.KEY_PASSWORD); 175 | kmf = getKeyManagerFactory(certFile, keyFile, keyPassword); 176 | } else { 177 | kmf = getKeyManagerFactory(key); 178 | } 179 | tmf = getTrustManagerFactory(trustStore); 180 | return buildSslContext(kmf.getKeyManagers(), tmf.getTrustManagers()); 181 | } 182 | tmf = getTrustManagerFactory(trustStore); 183 | return buildSslContext(null, tmf.getTrustManagers()); 184 | } 185 | return null; 186 | } 187 | 188 | private static HttpClient.Version getHttpVersion(String httpVersion) { 189 | if (OAuth2Constants.HTTP_2.equals(httpVersion)) { 190 | return HttpClient.Version.HTTP_2; 191 | } 192 | return HttpClient.Version.HTTP_1_1; 193 | } 194 | 195 | private static SSLContext initSslContext() throws Exception { 196 | TrustManager[] trustManagers = new TrustManager[]{ 197 | new X509TrustManager() { 198 | public X509Certificate[] getAcceptedIssuers() { 199 | return new X509Certificate[0]; 200 | } 201 | 202 | public void checkClientTrusted(X509Certificate[] certs, String authType) { 203 | } 204 | 205 | public void checkServerTrusted(X509Certificate[] certs, String authType) { 206 | } 207 | } 208 | }; 209 | return buildSslContext(null, trustManagers); 210 | } 211 | 212 | private static TrustManagerFactory getTrustManagerFactory(BString cert) throws Exception { 213 | Object publicKeyMap = Decode.decodeRsaPublicKeyFromCertFile(cert); 214 | if (publicKeyMap instanceof BMap) { 215 | X509Certificate x509Certificate = (X509Certificate) ((BMap) publicKeyMap).getNativeData( 216 | OAuth2Constants.NATIVE_DATA_PUBLIC_KEY_CERTIFICATE); 217 | KeyStore ts = KeyStore.getInstance(OAuth2Constants.PKCS12); 218 | ts.load(null, "".toCharArray()); 219 | ts.setCertificateEntry(UUID.randomUUID().toString(), x509Certificate); 220 | TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 221 | tmf.init(ts); 222 | return tmf; 223 | } 224 | throw new Exception("Failed to get the public key from Crypto API. " + 225 | ((BError) publicKeyMap).getErrorMessage().getValue()); 226 | } 227 | 228 | private static TrustManagerFactory getTrustManagerFactory(BMap trustStore) throws Exception { 229 | BString trustStorePath = trustStore.getStringValue(OAuth2Constants.PATH); 230 | BString trustStorePassword = trustStore.getStringValue(OAuth2Constants.PASSWORD); 231 | KeyStore ts = getKeyStore(trustStorePath, trustStorePassword); 232 | TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 233 | tmf.init(ts); 234 | return tmf; 235 | } 236 | 237 | private static KeyManagerFactory getKeyManagerFactory(BMap keyStore) throws Exception { 238 | BString keyStorePath = keyStore.getStringValue(OAuth2Constants.PATH); 239 | BString keyStorePassword = keyStore.getStringValue(OAuth2Constants.PASSWORD); 240 | KeyStore ks = getKeyStore(keyStorePath, keyStorePassword); 241 | KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 242 | kmf.init(ks, keyStorePassword.getValue().toCharArray()); 243 | return kmf; 244 | } 245 | 246 | private static KeyManagerFactory getKeyManagerFactory(BString certFile, BString keyFile, BString keyPassword) 247 | throws Exception { 248 | Object publicKey = Decode.decodeRsaPublicKeyFromCertFile(certFile); 249 | if (publicKey instanceof BMap) { 250 | X509Certificate publicCert = (X509Certificate) ((BMap) publicKey).getNativeData( 251 | OAuth2Constants.NATIVE_DATA_PUBLIC_KEY_CERTIFICATE); 252 | Object privateKeyMap = Decode.decodeRsaPrivateKeyFromKeyFile(keyFile, keyPassword); 253 | if (privateKeyMap instanceof BMap) { 254 | PrivateKey privateKey = (PrivateKey) ((BMap) privateKeyMap).getNativeData( 255 | OAuth2Constants.NATIVE_DATA_PRIVATE_KEY); 256 | KeyStore ks = KeyStore.getInstance(OAuth2Constants.PKCS12); 257 | ks.load(null, "".toCharArray()); 258 | ks.setKeyEntry(UUID.randomUUID().toString(), privateKey, "".toCharArray(), 259 | new X509Certificate[]{publicCert}); 260 | KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 261 | kmf.init(ks, "".toCharArray()); 262 | return kmf; 263 | } 264 | throw new Exception("Failed to get the private key from Crypto API. " + 265 | ((BError) privateKeyMap).getErrorMessage().getValue()); 266 | } 267 | throw new Exception("Failed to get the public key from Crypto API. " + 268 | ((BError) publicKey).getErrorMessage().getValue()); 269 | } 270 | 271 | private static KeyStore getKeyStore(BString path, BString password) throws Exception { 272 | try (FileInputStream is = new FileInputStream(path.getValue())) { 273 | char[] passphrase = password.getValue().toCharArray(); 274 | KeyStore ks = KeyStore.getInstance(OAuth2Constants.PKCS12); 275 | ks.load(is, passphrase); 276 | return ks; 277 | } 278 | } 279 | 280 | private static SSLContext buildSslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws Exception { 281 | SSLContext sslContext = SSLContext.getInstance(OAuth2Constants.TLS); 282 | sslContext.init(keyManagers, trustManagers, new SecureRandom()); 283 | return sslContext; 284 | } 285 | 286 | private static HttpClient buildHttpClient(String httpVersion, int connectionTimeout) { 287 | return HttpClient.newBuilder() 288 | .version(getHttpVersion(httpVersion)) 289 | .connectTimeout(Duration.ofMillis(connectionTimeout)) 290 | .build(); 291 | } 292 | 293 | private static HttpClient buildHttpClient(String httpVersion, SSLContext sslContext, int connectionTimeout) { 294 | return HttpClient.newBuilder() 295 | .version(getHttpVersion(httpVersion)) 296 | .sslContext(sslContext) 297 | .connectTimeout(Duration.ofMillis(connectionTimeout)) 298 | .build(); 299 | } 300 | 301 | private static HttpRequest buildHttpRequest(URI uri, String payload, int requestTimeout) { 302 | return HttpRequest.newBuilder() 303 | .uri(uri) 304 | .setHeader(OAuth2Constants.CONTENT_TYPE, OAuth2Constants.APPLICATION_FORM_URLENCODED) 305 | .POST(HttpRequest.BodyPublishers.ofString(payload)) 306 | .timeout(Duration.ofMillis(requestTimeout)) 307 | .build(); 308 | } 309 | 310 | private static HttpRequest buildHttpRequest(URI uri, String[] headers, String payload, int requestTimeout) { 311 | return HttpRequest.newBuilder() 312 | .uri(uri) 313 | .headers(headers) 314 | .setHeader(OAuth2Constants.CONTENT_TYPE, OAuth2Constants.APPLICATION_FORM_URLENCODED) 315 | .POST(HttpRequest.BodyPublishers.ofString(payload)) 316 | .timeout(Duration.ofMillis(requestTimeout)) 317 | .build(); 318 | } 319 | 320 | private static Object callEndpoint(Environment env, HttpClient client, HttpRequest request) { 321 | return env.yieldAndRun(() -> { 322 | try { 323 | HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); 324 | if (response.statusCode() >= 200 && response.statusCode() < 300) { 325 | return StringUtils.fromString(response.body()); 326 | } 327 | return createError("Failed to get a success response from the endpoint. Response code: '" + 328 | response.statusCode() + "'. Response body: '" + response.body() + "'"); 329 | } catch (IOException | InterruptedException e) { 330 | return createError("Failed to send the request to the endpoint. " + e.getMessage()); 331 | } 332 | }); 333 | } 334 | 335 | private static BMap getBMapValueIfPresent(BMap config, BString key) { 336 | return config.containsKey(key) ? (BMap) config.getMapValue(key) : null; 337 | } 338 | 339 | private static BString getBStringValueIfPresent(BMap config, BString key) { 340 | return config.containsKey(key) ? config.getStringValue(key) : null; 341 | } 342 | 343 | private static BError createError(String errMsg) { 344 | return ErrorCreator.createError(ModuleUtils.getModule(), OAuth2Constants.OAUTH2_ERROR_TYPE, 345 | StringUtils.fromString(errMsg), null, null); 346 | } 347 | 348 | private static int getConnectionTimeoutInMillis(BMap clientConfig) throws BError { 349 | double connectTimeoutInSeconds = clientConfig.containsKey(OAuth2Constants.CONNECTION_TIMEOUT) ? 350 | ((BDecimal) clientConfig.get(OAuth2Constants.CONNECTION_TIMEOUT)).floatValue() : 351 | ModuleUtils.getOauth2ConnectionTimeout(); 352 | if (connectTimeoutInSeconds <= 0) { 353 | throw createError("OAuth2 connection timeout must be greater than zero"); 354 | } 355 | return (int) (connectTimeoutInSeconds * 1000); 356 | } 357 | 358 | private static int getRequestTimeoutInMillis(BMap clientConfig) throws BError { 359 | double requestTimeoutInSeconds = clientConfig.containsKey(OAuth2Constants.REQUEST_TIMEOUT) ? 360 | ((BDecimal) clientConfig.get(OAuth2Constants.REQUEST_TIMEOUT)).floatValue() : 361 | ModuleUtils.getOauth2RequestTimeout(); 362 | if (requestTimeoutInSeconds <= 0) { 363 | throw createError("OAuth2 request timeout must be greater than zero"); 364 | } 365 | return (int) (requestTimeoutInSeconds * 1000); 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /docs/spec/spec.md: -------------------------------------------------------------------------------- 1 | # Specification: Ballerina OAuth2 Library 2 | 3 | _Owners_: @ldclakmal @shafreenAnfar 4 | _Reviewers_: @shafreenAnfar 5 | _Created_: 2021/10/01 6 | _Updated_: 2023/01/05 7 | _Edition_: Swan Lake 8 | 9 | ## Introduction 10 | This is the specification for the OAuth2 standard library of [Ballerina language](https://ballerina.io/), which is used for authorization of listeners and clients (HTTP, gRPC, GraphQL, WebSocket, WebSub, etc.). 11 | 12 | The OAuth2 library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag. 13 | 14 | If you have any feedback or suggestions about the library, start a discussion via a [GitHub issue](https://github.com/ballerina-platform/ballerina-standard-library/issues) or in the [Discord server](https://discord.gg/ballerinalang). Based on the outcome of the discussion, the specification and implementation can be updated. Community feedback is always welcome. Any accepted proposal, which affects the specification is stored under `/docs/proposals`. Proposals under discussion can be found with the label `type/proposal` in GitHub. 15 | 16 | The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug. 17 | 18 | ## Contents 19 | 20 | 1. [Overview](#1-overview) 21 | 2. [OAuth2](#2-oauth2) 22 | 3. [Listener Auth](#3-listener-auth) 23 | * 3.1. [OAuth2 Provider](#31-oauth2-provider) 24 | * 3.2. [OAuth2 Handler](#32-oauth2-handler) 25 | * 3.3. [Declarative Approach](#33-declarative-approach) 26 | * 3.4. [Imperative Approach](#34-imperative-approach) 27 | 4. [Client Auth](#4-client-auth) 28 | * 4.1. [OAuth2 Provider](#41-oauth2-provider) 29 | * 4.2. [OAuth2 Handler](#42-oauth2-handler) 30 | * 4.2.1. [Bearer Token](#421-bearer-token) 31 | * 4.2.2. [Grant Types](#422-grant-types) 32 | * 4.3. [Declarative Approach](#43-declarative-approach) 33 | * 4.4. [Imperative Approach](#44-imperative-approach) 34 | 5. [Samples](#5-samples) 35 | * 5.1. [Listener Auth](#51-listener-auth) 36 | * 5.1.1. [Declarative Approach (HTTP Listener)](#511-declarative-approach-http-listener) 37 | * 5.1.2. [Imperative Approach (HTTP Listener)](#512-imperative-approach-http-listener) 38 | * 5.2. [Client Auth](#52-client-auth) 39 | * 5.2.1. [Declarative Approach (HTTP Client)](#521-declarative-approach-http-client) 40 | * 5.2.1.1. [Bearer Token](#5211-bearer-token) 41 | * 5.2.1.2. [Grant Types](#5212-grant-types) 42 | * 5.2.2. [Imperative Approach (HTTP Client)](#522-imperative-approach-http-client) 43 | * 5.2.2.1. [Bearer Token](#5221-bearer-token) 44 | * 5.2.2.2. [Grant Types](#5222-grant-types) 45 | 46 | ## 1. Overview 47 | This specification elaborates on OAuth2 authorization for all the Ballerina listeners and 48 | clients. The HTTP, gRPC, GraphQL, WebSocket, WebSub protocol-based listeners and clients are secured according to this 49 | specification. 50 | 51 | This has a number of design principles: 52 | - **Listener auth**: This refers to the authentication and authorization of the listener as defined in 53 | [Ballerina 2021R1 Section 5.7.4](https://ballerina.io/spec/lang/2021R1/#section_5.7.4). The inbound requests/messages 54 | independent of the transport protocol are authenticated and authorized according to the configured authentication 55 | protocol and related configurations. 56 | - **Client auth**: This refers to the authentication of the client as defined in 57 | [Ballerina 2021R1 Section 7.9](https://ballerina.io/spec/lang/2021R1/#section_7.9). The outbound requests/messages 58 | independent of the transport protocol are enriched according to the configured authentication protocol and related 59 | configurations. 60 | - **Auth provider**: This is the entity that is responsible for providing all the auth protocol-related implementations. 61 | - **Auth handler**: This is the entity that is responsible for handling the security of the API based on the transport 62 | protocol and with the use of provider APIs. This API gets the credentials and required configurations as user inputs 63 | and returns the authentication protocol-related information. Internally, these APIs call the provider APIs of the 64 | relevant authentication protocol. 65 | - **Declarative approach**: This is also known as the configuration-driven approach, which is used for simple use cases, 66 | where users have to provide a set of configurations and do not need to be worried more about how authentication and 67 | authorization works. 68 | - **Imperative approach**: This is also known as the code-driven approach, which is used for advanced use cases, where 69 | users need to be worried more about how authentication and authorization work and need to have further customizations. 70 | 71 | ## 2. OAuth2 72 | OAuth2 protocol defines the credential as an access token which is defined in 73 | [RFC6749 Section 1.4](https://datatracker.ietf.org/doc/html/rfc6749#section-1.4). 74 | 75 | ## 3. Listener Auth 76 | This refers to the authentication and authorization of the listener as defined in 77 | [Ballerina 2021R1 Section 5.7.4](https://ballerina.io/spec/lang/2021R1/#section_5.7.4). The inbound requests/messages 78 | independent of the transport protocol are authenticated and authorized according to the configured authentication 79 | protocol and related configurations. 80 | 81 | ### 3.1. OAuth2 Provider 82 | The OAuth2 Provider has an API to authorize the OAuth2 credential. The `IntrospectionConfig` record is used to provide 83 | the configuration related to the introspection server which is being called at the time of credential validation. This 84 | returns the `IntrospectionResponse` which consists of all the available information of the introspection server response. 85 | 86 | ```ballerina 87 | import ballerina/cache; 88 | 89 | public type IntrospectionConfig record { 90 | string url; 91 | string tokenTypeHint?; 92 | map optionalParams?; 93 | cache:CacheConfig cacheConfig?; 94 | decimal defaultTokenExpTime = 3600; 95 | ClientConfiguration clientConfig = {}; 96 | }; 97 | 98 | public type IntrospectionResponse record { 99 | boolean active; 100 | string scope?; 101 | string clientId?; 102 | string username?; 103 | string tokenType?; 104 | int exp?; 105 | int iat?; 106 | int nbf?; 107 | string sub?; 108 | string aud?; 109 | string iss?; 110 | string jti?; 111 | }; 112 | 113 | public class ListenerOAuth2Provider { 114 | 115 | public function init(IntrospectionConfig config) { 116 | // init OAuth2 provider 117 | } 118 | 119 | public function authorize(string credential, map? optionalParams = ()) returns IntrospectionResponse|Error { 120 | // validate the credential against introspection endpoint 121 | } 122 | } 123 | ``` 124 | 125 | ### 3.2. OAuth2 Handler 126 | 127 | > **NOTE**: Since the auth handlers are tightly bound with the transport protocol, for the explanation of the concept, 128 | > all the samples are created for HTTP transport protocol hereinafter. 129 | 130 | The OAuth2 Handler has an API to authorize the HTTP request, headers of the HTTP request, or the credential as defined 131 | in [RFC6750 Section 2.1](https://datatracker.ietf.org/doc/html/rfc6750#section-2.1). This API is also used to authorize 132 | the user against the expected scope or scopes. The `OAuth2IntrospectionConfig` record is used to provide the 133 | configuration related to the introspection server which is being called at the time of credential validation along with 134 | the `scopeKey` which defines the claim used for scopes. This returns the `IntrospectionResponse` which consists of all 135 | the available information of the introspection server response or `Unauthorized` in case of authentication failure or 136 | `Forbidden` in case of authorization failure. 137 | 138 | ```ballerina 139 | import ballerina/oauth2; 140 | 141 | public type OAuth2IntrospectionConfig record {| 142 | *oauth2:IntrospectionConfig; 143 | string scopeKey = "scope"; 144 | |}; 145 | 146 | public client class ListenerOAuth2Handler { 147 | 148 | private final oauth2:ListenerOAuth2Provider provider; 149 | 150 | public function init(OAuth2IntrospectionConfig config) { 151 | self.provider = new (config); 152 | } 153 | 154 | remote function authorize(Request|Headers|string data, string|string[]? expectedScopes = (), 155 | map? optionalParams = ()) returns oauth2:IntrospectionResponse|Unauthorized|Forbidden { 156 | // extract the credential from data 157 | oauth2:IntrospectionResponse|oauth2:Error response = self.provider.authorize(credential, optionalParams); 158 | if (response is oauth2:Error) { 159 | // return `Unauthorized` 160 | } 161 | // match the scopes with the provided `expectedScopes` 162 | // if not matched return `Forbidden` 163 | return response; 164 | } 165 | } 166 | ``` 167 | 168 | ### 3.3. Declarative Approach 169 | This is also known as the configuration-driven approach, which is used for simple use cases, where users have to 170 | provide a set of configurations and do not need to be worried more about how authentication and authorization works. 171 | The user does not have full control over the configuration-driven approach. 172 | 173 | The service and/or resource configurations are used to define the authentication and authorization configurations. 174 | Users can configure the configurations needed for different authentication schemes and configurations needed for 175 | authorizations of each authentication scheme. Also, the configurations can be provided at both the service and resource 176 | levels. The priority will be given from bottom to top. Then, the auth handler creation and request 177 | authentication/authorization is handled internally without user intervention. The requests that succeeded both 178 | authentication and/or authorization phases according to the configurations will be passed to the business logic layer. 179 | 180 | ### 3.4. Imperative Approach 181 | This is also known as the code-driven approach, which is used for advanced use cases, where users need to be worried 182 | more about how authentication and authorization work and need to have further customizations. The user has full control 183 | of the code-driven approach. The handler creation and authentication/authorization calls are made by the user at the 184 | business logic layer. 185 | 186 | ## 4. Client Auth 187 | This refers to the authentication of the client as defined in 188 | [Ballerina 2021R1 Section 7.9](https://ballerina.io/spec/lang/2021R1/#section_7.9). The outbound requests/messages 189 | independent of the transport protocol are enriched according to the configured authentication protocol and related 190 | configurations. 191 | 192 | ### 4.1. OAuth2 Provider 193 | The OAuth2 Provider has an API to generate the OAuth2 credential. The `ClientCredentialsGrantConfig`, 194 | `PasswordGrantConfig`, `RefreshTokenGrantConfig`, or `JwtBearerGrantConfig` records are used to provide the 195 | configuration related to the OAuth2 grant type used for access token generation. This returns the generated access token. 196 | 197 | ```ballerina 198 | public type ClientCredentialsGrantConfig record {| 199 | string tokenUrl; 200 | string clientId; 201 | string clientSecret; 202 | string|string[] scopes?; 203 | decimal defaultTokenExpTime = 3600; 204 | decimal clockSkew = 0; 205 | map optionalParams?; 206 | CredentialBearer credentialBearer = AUTH_HEADER_BEARER; 207 | ClientConfiguration clientConfig = {}; 208 | |}; 209 | 210 | public type PasswordGrantConfig record {| 211 | string tokenUrl; 212 | string username; 213 | string password; 214 | string clientId?; 215 | string clientSecret?; 216 | string|string[] scopes?; 217 | RefreshConfig|INFER_REFRESH_CONFIG refreshConfig?; 218 | decimal defaultTokenExpTime = 3600; 219 | decimal clockSkew = 0; 220 | map optionalParams?; 221 | CredentialBearer credentialBearer = AUTH_HEADER_BEARER; 222 | ClientConfiguration clientConfig = {}; 223 | |}; 224 | 225 | public type RefreshTokenGrantConfig record {| 226 | string refreshUrl; 227 | string refreshToken; 228 | string clientId; 229 | string clientSecret; 230 | string|string[] scopes?; 231 | decimal defaultTokenExpTime = 3600; 232 | decimal clockSkew = 0; 233 | map optionalParams?; 234 | CredentialBearer credentialBearer = AUTH_HEADER_BEARER; 235 | ClientConfiguration clientConfig = {}; 236 | |}; 237 | 238 | public type JwtBearerGrantConfig record {| 239 | string tokenUrl; 240 | string assertion; 241 | string clientId?; 242 | string clientSecret?; 243 | string|string[] scopes?; 244 | decimal defaultTokenExpTime = 3600; 245 | decimal clockSkew = 0; 246 | map optionalParams?; 247 | CredentialBearer credentialBearer = AUTH_HEADER_BEARER; 248 | ClientConfiguration clientConfig = {}; 249 | |}; 250 | 251 | public type GrantConfig ClientCredentialsGrantConfig|PasswordGrantConfig|RefreshTokenGrantConfig|JwtBearerGrantConfig; 252 | 253 | public class ClientOAuth2Provider { 254 | 255 | public function init(GrantConfig config) { 256 | // init OAuth2 provider 257 | } 258 | 259 | public function generateToken() returns string|Error { 260 | // get the OAuth2 token by calling to IDP with given configurations 261 | } 262 | } 263 | ``` 264 | 265 | ### 4.2. OAuth2 Handler 266 | 267 | > **NOTE**: Since the auth handlers are tightly bound with the transport protocol, for the explanation of the concept, 268 | > all the samples are created for HTTP transport protocol hereinafter. 269 | 270 | #### 4.2.1. Bearer Token 271 | The Bearer Token Auth Handler has an API to enrich the HTTP request as defined in 272 | [RFC6750 Section 2.1](https://datatracker.ietf.org/doc/html/rfc6750#section-2.1). The `BearerTokenConfig` record is 273 | used to provide the configuration related to the access token. This returns the enriched `Request` with headers or 274 | `Error` in case of failure. 275 | 276 | ```ballerina 277 | public type BearerTokenConfig record {| 278 | string token; 279 | |}; 280 | 281 | public class ClientBearerTokenAuthHandler { 282 | 283 | private final BearerTokenConfig & readonly config; 284 | 285 | public function init(BearerTokenConfig config) { 286 | self.config = config.cloneReadOnly(); 287 | } 288 | 289 | public function enrich(Request req) returns Request|Error { 290 | // set the token as the `Authorization: Bearer ` header 291 | } 292 | } 293 | ``` 294 | 295 | #### 4.2.2. Grant Types 296 | The OAuth2 Handler has an API to enrich the HTTP request as defined in 297 | [RFC6750 Section 2.1](https://datatracker.ietf.org/doc/html/rfc6750#section-2.1). The 298 | `OAuth2ClientCredentialsGrantConfig`, `OAuth2PasswordGrantConfig`, `OAuth2RefreshTokenGrantConfig`, or 299 | `OAuth2JwtBearerGrantConfig` records are used to provide the configuration related to the OAuth2 grant type used for 300 | access token generation. This returns the enriched `Request` with headers or `Error` in case of failure. 301 | 302 | ```ballerina 303 | import ballerina/oauth2; 304 | 305 | public type OAuth2GrantConfig OAuth2ClientCredentialsGrantConfig|OAuth2PasswordGrantConfig| 306 | OAuth2RefreshTokenGrantConfig|OAuth2JwtBearerGrantConfig; 307 | 308 | public type OAuth2ClientCredentialsGrantConfig record {| 309 | *oauth2:ClientCredentialsGrantConfig; 310 | |}; 311 | 312 | public type OAuth2PasswordGrantConfig record {| 313 | *oauth2:PasswordGrantConfig; 314 | |}; 315 | 316 | public type OAuth2RefreshTokenGrantConfig record {| 317 | *oauth2:RefreshTokenGrantConfig; 318 | |}; 319 | 320 | public type OAuth2JwtBearerGrantConfig record {| 321 | *oauth2:JwtBearerGrantConfig; 322 | |}; 323 | 324 | public client class ClientOAuth2Handler { 325 | 326 | private final oauth2:ClientOAuth2Provider provider; 327 | 328 | public function init(OAuth2GrantConfig config) { 329 | self.provider = new (config); 330 | } 331 | 332 | remote function enrich(Request req) returns Request|Error { 333 | string|oauth2:Error token = self.provider.generateToken(); 334 | // set the token as the `Authorization: Bearer ` header 335 | } 336 | } 337 | ``` 338 | 339 | ### 4.3. Declarative Approach 340 | This is also known as a configuration-driven approach, which is used for simple use cases, where users have to provide 341 | a set of configurations and do not need to be worried more about how authentication works. The user does not have full 342 | control over the configuration-driven approach. 343 | 344 | The client configurations are used to define the authentication configurations. Users can configure the configurations 345 | needed for different authentication schemes. Then, the auth handler creation and request enrichment is handled 346 | internally without user intervention. 347 | 348 | ### 4.4. Imperative Approach 349 | This is also known as the code-driven approach, which is used for advanced use cases, where users need to be worried 350 | more about how authentication works and need to have further customizations. The user has full control of the 351 | code-driven approach. The handler creation and request enrichment calls are made by the user at the business logic layer. 352 | 353 | ## 5. Samples 354 | 355 | ### 5.1. Listener Auth 356 | 357 | #### 5.1.1. Declarative Approach (HTTP Listener) 358 | 359 | ```ballerina 360 | import ballerina/http; 361 | 362 | @http:ServiceConfig { 363 | auth: [ 364 | { 365 | oauth2IntrospectionConfig: { 366 | url: "https://localhost:9445/oauth2/introspect", 367 | tokenTypeHint: "access_token", 368 | scopeKey: "scp", 369 | clientConfig: { 370 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 371 | secureSocket: { 372 | cert: "/path/to/public.crt" 373 | } 374 | } 375 | }, 376 | scopes: "admin" 377 | } 378 | ] 379 | } 380 | service /foo on new http:Listener(9090) { 381 | resource function get bar() returns string { 382 | return "Hello, World!"; 383 | } 384 | } 385 | ``` 386 | 387 | #### 5.1.2. Imperative Approach (HTTP Listener) 388 | 389 | ```ballerina 390 | import ballerina/http; 391 | import ballerina/oauth2; 392 | 393 | http:OAuth2IntrospectionConfig config = { 394 | url: "https://localhost:8080/oauth2/introspect", 395 | tokenTypeHint: "access_token" 396 | }; 397 | http:ListenerOAuth2Handler handler = new (config); 398 | 399 | service /foo on new http:Listener(9090) { 400 | resource function post bar(@http:Header string authorization) returns string|http:Unauthorized|http:Forbidden { 401 | oauth2:IntrospectionResponse|http:Unauthorized|http:Forbidden auth = handler->authorize(authorization, "admin"); 402 | if (auth is http:Unauthorized || auth is http:Forbidden) { 403 | return auth; 404 | } 405 | // business logic 406 | } 407 | } 408 | ``` 409 | 410 | ### 5.2. Client Auth 411 | 412 | #### 5.2.1. Declarative Approach (HTTP Client) 413 | 414 | ##### 5.2.1.1. Bearer Token 415 | 416 | ```ballerina 417 | import ballerina/http; 418 | 419 | http:Client c = check new ("https://localhost:9090", 420 | auth = { 421 | token: "56ede317-4511-44b4-8579-a08f094ee8c5" 422 | } 423 | ); 424 | 425 | public function main() returns error? { 426 | http:Request req = new; 427 | json response = check c->post("/foo/bar", req); 428 | // evaluate response 429 | } 430 | 431 | ``` 432 | 433 | ##### 5.2.1.2. Grant Types 434 | 435 | ```ballerina 436 | import ballerina/http; 437 | 438 | http:OAuth2ClientCredentialsGrantConfig config = { 439 | tokenUrl: "https://localhost:8080/oauth2/token/authorize", 440 | clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxS", 441 | clientSecret: "9205371918321623741" 442 | }; 443 | 444 | http:Client c = check new ("https://localhost:9090", auth = config); 445 | 446 | public function main() returns error? { 447 | http:Request req = new; 448 | json response = check c->post("/foo/bar", req); 449 | // evaluate response 450 | } 451 | ``` 452 | 453 | #### 5.2.2. Imperative Approach (HTTP Client) 454 | 455 | ##### 5.2.2.1. Bearer Token 456 | 457 | ```ballerina 458 | import ballerina/http; 459 | 460 | http:BearerTokenConfig config = { 461 | token: "JhbGciOiJIIiwiaWF0IjUzI1NiIsInR5cCI6IkpXVCJ9WIiOiIxMjM0NTY3ODkwI" 462 | }; 463 | http:ClientBearerTokenAuthHandler handler = new (config); 464 | 465 | http:Client c = check new ("https://localhost:9090"); 466 | 467 | public function main() returns error? { 468 | http:Request req = new; 469 | req = check handler.enrich(req); 470 | json response = check c->post("/foo/bar", req); 471 | // evaluate response 472 | } 473 | ``` 474 | 475 | ##### 5.2.2.2. Grant Types 476 | 477 | ```ballerina 478 | import ballerina/http; 479 | 480 | http:OAuth2ClientCredentialsGrantConfig config = { 481 | tokenUrl: "https://localhost:8080/oauth2/token/authorize", 482 | clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxS", 483 | clientSecret: "9205371918321623741" 484 | }; 485 | http:ClientOAuth2Handler handler = new (config); 486 | 487 | http:Client c = check new ("https://localhost:9090"); 488 | 489 | public function main() returns error? { 490 | http:Request req = new; 491 | req = check handler->enrich(req); 492 | json response = check c->post("/foo/bar", req); 493 | // evaluate response 494 | } 495 | ``` 496 | -------------------------------------------------------------------------------- /ballerina/tests/listener_oauth2_provider_test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | // NOTE: All the tokens/credentials used in this test are dummy tokens/credentials and used only for testing purposes. 18 | 19 | import ballerina/test; 20 | import ballerina/cache; 21 | 22 | isolated function getAccessToken() returns string|Error { 23 | ClientCredentialsGrantConfig config = { 24 | tokenUrl: "https://localhost:9443/oauth2/token", 25 | clientId: "uDMwA4hKR9H3deeXxvNf4sSU0i4a", 26 | clientSecret: "8FOUOKUQfOp47pUfJCsPA5X4clga", 27 | scopes: "view-order", 28 | clientConfig: { 29 | secureSocket: { 30 | cert: WSO2_PUBLIC_CERT_PATH 31 | } 32 | } 33 | }; 34 | ClientOAuth2Provider provider = new(config); 35 | return provider.generateToken(); 36 | } 37 | 38 | // Test the introspection request with successful token 39 | @test:Config { 40 | groups: ["skipOnWindows"] 41 | } 42 | isolated function testTokenIntrospection1() returns Error? { 43 | string accessToken = check getAccessToken(); 44 | IntrospectionConfig config = { 45 | url: "https://localhost:9443/oauth2/introspect", 46 | clientConfig: { 47 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 48 | secureSocket: { 49 | cert: WSO2_PUBLIC_CERT_PATH 50 | } 51 | } 52 | }; 53 | ListenerOAuth2Provider provider = new(config); 54 | IntrospectionResponse response = check provider.authorize(accessToken); 55 | test:assertTrue(response.active); 56 | test:assertEquals(response?.scope, "view-order"); 57 | test:assertEquals(response?.clientId, "uDMwA4hKR9H3deeXxvNf4sSU0i4a"); 58 | test:assertEquals(response?.username, "admin@carbon.super"); 59 | test:assertEquals(response?.tokenType, "Bearer"); 60 | test:assertTrue(response?.exp is int); 61 | test:assertTrue(response?.iat is int); 62 | test:assertTrue(response?.nbf is int); 63 | } 64 | 65 | // Test the introspection request with successful token with cache configurations 66 | @test:Config { 67 | groups: ["skipOnWindows"] 68 | } 69 | isolated function testTokenIntrospection2() returns Error? { 70 | string accessToken = check getAccessToken(); 71 | IntrospectionConfig config = { 72 | url: "https://localhost:9443/oauth2/introspect", 73 | cacheConfig: { 74 | capacity: 10, 75 | evictionFactor: 0.25, 76 | evictionPolicy: cache:LRU, 77 | defaultMaxAge: -1, 78 | cleanupInterval: 3600 79 | }, 80 | clientConfig: { 81 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 82 | secureSocket: { 83 | cert: WSO2_PUBLIC_CERT_PATH 84 | } 85 | } 86 | }; 87 | ListenerOAuth2Provider provider = new(config); 88 | IntrospectionResponse response = check provider.authorize(accessToken); 89 | test:assertTrue(response.active); 90 | test:assertEquals(response?.scope, "view-order"); 91 | test:assertEquals(response?.clientId, "uDMwA4hKR9H3deeXxvNf4sSU0i4a"); 92 | test:assertEquals(response?.username, "admin@carbon.super"); 93 | test:assertEquals(response?.tokenType, "Bearer"); 94 | test:assertTrue(response?.exp is int); 95 | test:assertTrue(response?.iat is int); 96 | test:assertTrue(response?.nbf is int); 97 | 98 | // Get the response using the cache 99 | response = check provider.authorize(accessToken); 100 | test:assertTrue(response.active); 101 | test:assertEquals(response?.scope, "view-order"); 102 | test:assertEquals(response?.clientId, "uDMwA4hKR9H3deeXxvNf4sSU0i4a"); 103 | test:assertEquals(response?.username, "admin@carbon.super"); 104 | test:assertEquals(response?.tokenType, "Bearer"); 105 | test:assertTrue(response?.exp is int); 106 | test:assertTrue(response?.iat is int); 107 | test:assertTrue(response?.nbf is int); 108 | } 109 | 110 | // Test the introspection request with invalid token 111 | @test:Config { 112 | groups: ["skipOnWindows"] 113 | } 114 | isolated function testTokenIntrospection3() returns Error? { 115 | string accessToken = "invalid_token"; 116 | IntrospectionConfig config = { 117 | url: "https://localhost:9443/oauth2/introspect", 118 | clientConfig: { 119 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 120 | secureSocket: { 121 | cert: WSO2_PUBLIC_CERT_PATH 122 | } 123 | } 124 | }; 125 | ListenerOAuth2Provider provider = new(config); 126 | IntrospectionResponse response = check provider.authorize(accessToken); 127 | test:assertFalse(response.active); 128 | } 129 | 130 | // Test the introspection request with empty token 131 | @test:Config {} 132 | isolated function testTokenIntrospection4() { 133 | string accessToken = ""; 134 | IntrospectionConfig config = { 135 | url: "https://localhost:9443/oauth2/introspect", 136 | clientConfig: { 137 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 138 | secureSocket: { 139 | cert: WSO2_PUBLIC_CERT_PATH 140 | } 141 | } 142 | }; 143 | ListenerOAuth2Provider provider = new(config); 144 | IntrospectionResponse|Error response = provider.authorize(accessToken); 145 | if response is Error { 146 | assertContains(response, "Credential cannot be empty."); 147 | } else { 148 | test:assertFail("Expected error not found."); 149 | } 150 | } 151 | 152 | // Test the introspection request with successful token without authenticating the client 153 | @test:Config { 154 | groups: ["skipOnWindows"] 155 | } 156 | isolated function testTokenIntrospection5() returns Error? { 157 | string accessToken = check getAccessToken(); 158 | IntrospectionConfig config = { 159 | url: "https://localhost:9443/oauth2/introspect", 160 | clientConfig: { 161 | secureSocket: { 162 | cert: WSO2_PUBLIC_CERT_PATH 163 | } 164 | } 165 | }; 166 | ListenerOAuth2Provider provider = new(config); 167 | IntrospectionResponse|Error response = provider.authorize(accessToken); 168 | if response is Error { 169 | assertContains(response, "Failed to call the introspection endpoint 'https://localhost:9443/oauth2/introspect'."); 170 | } else { 171 | test:assertFail("Expected error not found."); 172 | } 173 | } 174 | 175 | // Test the introspection request with successful token with invalid OAuth2 client credentials grant type 176 | @test:Config { 177 | groups: ["skipOnWindows"] 178 | } 179 | isolated function testTokenIntrospection6() { 180 | IntrospectionConfig config = { 181 | url: "https://localhost:9443/oauth2/introspect", 182 | clientConfig: { 183 | auth: { 184 | tokenUrl: "https://localhost:9443/oauth2/token", 185 | clientId: "invalid_client_id", 186 | clientSecret: "invalid_client_secret", 187 | clientConfig: { 188 | secureSocket: { 189 | cert: WSO2_PUBLIC_CERT_PATH 190 | } 191 | } 192 | }, 193 | secureSocket: { 194 | cert: WSO2_PUBLIC_CERT_PATH 195 | } 196 | } 197 | }; 198 | ListenerOAuth2Provider|error provider = trap new(config); 199 | if provider is error { 200 | assertContains(provider, "{\"error_description\":\"A valid OAuth client could not be found for client_id: invalid_client_id\",\"error\":\"invalid_client\"}"); 201 | } else { 202 | test:assertFail("Expected error not found."); 203 | } 204 | } 205 | 206 | // Test the introspection request with successful token with invalid OAuth2 password grant type 207 | @test:Config { 208 | groups: ["skipOnWindows"] 209 | } 210 | isolated function testTokenIntrospection7() { 211 | IntrospectionConfig config = { 212 | url: "https://localhost:9443/oauth2/introspect", 213 | clientConfig: { 214 | auth: { 215 | tokenUrl: "https://localhost:9443/oauth2/token", 216 | username: "invalid_username", 217 | password: "invalid_password", 218 | clientId: "invalid_client_id", 219 | clientSecret: "invalid_client_secret", 220 | scopes: "view-order", 221 | clientConfig: { 222 | secureSocket: { 223 | cert: WSO2_PUBLIC_CERT_PATH 224 | } 225 | } 226 | }, 227 | secureSocket: { 228 | cert: WSO2_PUBLIC_CERT_PATH 229 | } 230 | } 231 | }; 232 | ListenerOAuth2Provider|error provider = trap new(config); 233 | if provider is error { 234 | assertContains(provider, "{\"error_description\":\"A valid OAuth client could not be found for client_id: invalid_client_id\",\"error\":\"invalid_client\"}"); 235 | } else { 236 | test:assertFail("Expected error not found."); 237 | } 238 | } 239 | 240 | // Test the introspection request with successful token and with all the configurations (keystore & truststore) 241 | @test:Config { 242 | groups: ["skipOnWindows"] 243 | } 244 | isolated function testTokenIntrospection8() returns Error? { 245 | string accessToken = "56ede317-4511-44b4-8579-a08f094ee8c5"; 246 | IntrospectionConfig config = { 247 | url: "https://localhost:9445/oauth2/introspect", 248 | tokenTypeHint: "access_token", 249 | optionalParams: { 250 | "client": "ballerina" 251 | }, 252 | cacheConfig: { 253 | capacity: 10, 254 | evictionFactor: 0.25, 255 | evictionPolicy: cache:LRU, 256 | defaultMaxAge: -1, 257 | cleanupInterval: 3600 258 | }, 259 | defaultTokenExpTime: 3600, 260 | clientConfig: { 261 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 262 | customPayload: "example_payload_key=example_payload_value", 263 | secureSocket: { 264 | cert: { 265 | path: TRUSTSTORE_PATH, 266 | password: "ballerina" 267 | }, 268 | key: { 269 | path: KEYSTORE_PATH, 270 | password: "ballerina" 271 | } 272 | } 273 | } 274 | }; 275 | ListenerOAuth2Provider provider = new(config); 276 | IntrospectionResponse response = check provider.authorize(accessToken, {"example_key": "example_value"}); 277 | test:assertTrue(response.active); 278 | test:assertEquals(response?.scope, "read write dolphin"); 279 | test:assertEquals(response?.clientId, "l238j323ds-23ij4"); 280 | test:assertEquals(response?.username, "jdoe"); 281 | test:assertEquals(response?.tokenType, "token_type"); 282 | test:assertTrue(response?.exp is int); 283 | test:assertTrue(response?.iat is int); 284 | test:assertTrue(response?.nbf is int); 285 | test:assertEquals(response?.sub, "Z5O3upPC88QrAjx00dis"); 286 | test:assertEquals(response?.aud, "https://protected.example.net/resource"); 287 | test:assertEquals(response?.iss, "https://server.example.com/"); 288 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 289 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 290 | } 291 | 292 | // Test the introspection request with successful token and with all the configurations (private/public key) 293 | @test:Config { 294 | groups: ["skipOnWindows"] 295 | } 296 | isolated function testTokenIntrospection9() returns Error? { 297 | string accessToken = "56ede317-4511-44b4-8579-a08f094ee8c5"; 298 | IntrospectionConfig config = { 299 | url: "https://localhost:9445/oauth2/introspect", 300 | tokenTypeHint: "access_token", 301 | optionalParams: { 302 | "client": "ballerina" 303 | }, 304 | cacheConfig: { 305 | capacity: 10, 306 | evictionFactor: 0.25, 307 | evictionPolicy: cache:LRU, 308 | defaultMaxAge: -1, 309 | cleanupInterval: 3600 310 | }, 311 | defaultTokenExpTime: 3600, 312 | clientConfig: { 313 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 314 | customPayload: "example_payload_key=example_payload_value", 315 | secureSocket: { 316 | cert: PUBLIC_CERT_PATH, 317 | key: { 318 | certFile: PUBLIC_CERT_PATH, 319 | keyFile: ENCRYPTED_PRIVATE_KEY_PATH, 320 | keyPassword: "ballerina" 321 | } 322 | } 323 | } 324 | }; 325 | ListenerOAuth2Provider provider = new(config); 326 | IntrospectionResponse response = check provider.authorize(accessToken, {"example_key": "example_value"}); 327 | test:assertTrue(response.active); 328 | test:assertEquals(response?.scope, "read write dolphin"); 329 | test:assertEquals(response?.clientId, "l238j323ds-23ij4"); 330 | test:assertEquals(response?.username, "jdoe"); 331 | test:assertEquals(response?.tokenType, "token_type"); 332 | test:assertTrue(response?.exp is int); 333 | test:assertTrue(response?.iat is int); 334 | test:assertTrue(response?.nbf is int); 335 | test:assertEquals(response?.sub, "Z5O3upPC88QrAjx00dis"); 336 | test:assertEquals(response?.aud, "https://protected.example.net/resource"); 337 | test:assertEquals(response?.iss, "https://server.example.com/"); 338 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 339 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 340 | } 341 | 342 | // Test the introspection request with successful token and with all the configurations (disable SSL) 343 | @test:Config { 344 | groups: ["skipOnWindows"] 345 | } 346 | isolated function testTokenIntrospection10() returns Error? { 347 | string accessToken = "56ede317-4511-44b4-8579-a08f094ee8c5"; 348 | IntrospectionConfig config = { 349 | url: "https://localhost:9445/oauth2/introspect", 350 | tokenTypeHint: "access_token", 351 | optionalParams: { 352 | "client": "ballerina" 353 | }, 354 | cacheConfig: { 355 | capacity: 10, 356 | evictionFactor: 0.25, 357 | evictionPolicy: cache:LRU, 358 | defaultMaxAge: -1, 359 | cleanupInterval: 3600 360 | }, 361 | defaultTokenExpTime: 3600, 362 | clientConfig: { 363 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 364 | customPayload: "example_payload_key=example_payload_value", 365 | secureSocket: { 366 | disable: true 367 | } 368 | } 369 | }; 370 | ListenerOAuth2Provider provider = new(config); 371 | IntrospectionResponse response = check provider.authorize(accessToken, {"example_key": "example_value"}); 372 | test:assertTrue(response.active); 373 | test:assertEquals(response?.scope, "read write dolphin"); 374 | test:assertEquals(response?.clientId, "l238j323ds-23ij4"); 375 | test:assertEquals(response?.username, "jdoe"); 376 | test:assertEquals(response?.tokenType, "token_type"); 377 | test:assertTrue(response?.exp is int); 378 | test:assertTrue(response?.iat is int); 379 | test:assertTrue(response?.nbf is int); 380 | test:assertEquals(response?.sub, "Z5O3upPC88QrAjx00dis"); 381 | test:assertEquals(response?.aud, "https://protected.example.net/resource"); 382 | test:assertEquals(response?.iss, "https://server.example.com/"); 383 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 384 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 385 | } 386 | 387 | // Test the introspection request with successful token and with all the configurations (empty secure socket) 388 | @test:Config {} 389 | isolated function testTokenIntrospection11() { 390 | string accessToken = "56ede317-4511-44b4-8579-a08f094ee8c5"; 391 | IntrospectionConfig config = { 392 | url: "https://localhost:9445/oauth2/introspect", 393 | tokenTypeHint: "access_token", 394 | optionalParams: { 395 | "client": "ballerina" 396 | }, 397 | cacheConfig: { 398 | capacity: 10, 399 | evictionFactor: 0.25, 400 | evictionPolicy: cache:LRU, 401 | defaultMaxAge: -1, 402 | cleanupInterval: 3600 403 | }, 404 | defaultTokenExpTime: 3600, 405 | clientConfig: { 406 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 407 | customPayload: "example_payload_key=example_payload_value", 408 | secureSocket: { 409 | } 410 | } 411 | }; 412 | ListenerOAuth2Provider provider = new(config); 413 | IntrospectionResponse|Error response = provider.authorize(accessToken, {"example_key": "example_value"}); 414 | if response is Error { 415 | assertContains(response, "Need to configure 'crypto:TrustStore' or 'cert' with client SSL certificates file."); 416 | } else { 417 | test:assertFail("Expected error not found."); 418 | } 419 | } 420 | 421 | @test:Config { 422 | groups: ["skipOnWindows"] 423 | } 424 | isolated function testTokenIntrospectionRequestWithoutUrlScheme() returns Error? { 425 | string accessToken = "56ede317-4511-44b4-8579-a08f094ee8c5"; 426 | IntrospectionConfig config = { 427 | url: "localhost:9444/oauth2/introspect", 428 | tokenTypeHint: "access_token", 429 | clientConfig: { 430 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="} 431 | } 432 | }; 433 | ListenerOAuth2Provider provider = new(config); 434 | IntrospectionResponse response = check provider.authorize(accessToken, {"example_key": "example_value"}); 435 | test:assertTrue(response.active); 436 | test:assertEquals(response?.scope, "read write dolphin"); 437 | test:assertEquals(response?.clientId, "l238j323ds-23ij4"); 438 | test:assertEquals(response?.username, "jdoe"); 439 | test:assertEquals(response?.tokenType, "token_type"); 440 | test:assertTrue(response?.exp is int); 441 | test:assertTrue(response?.iat is int); 442 | test:assertTrue(response?.nbf is int); 443 | test:assertEquals(response?.sub, "Z5O3upPC88QrAjx00dis"); 444 | test:assertEquals(response?.aud, "https://protected.example.net/resource"); 445 | test:assertEquals(response?.iss, "https://server.example.com/"); 446 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 447 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 448 | } 449 | 450 | @test:Config { 451 | groups: ["skipOnWindows"] 452 | } 453 | isolated function testTokenIntrospectionRequestWithHttpUrlScheme() returns Error? { 454 | string accessToken = "56ede317-4511-44b4-8579-a08f094ee8c5"; 455 | IntrospectionConfig config = { 456 | url: "http://localhost:9444/oauth2/introspect", 457 | tokenTypeHint: "access_token", 458 | clientConfig: { 459 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="} 460 | } 461 | }; 462 | ListenerOAuth2Provider provider = new(config); 463 | IntrospectionResponse response = check provider.authorize(accessToken, {"example_key": "example_value"}); 464 | test:assertTrue(response.active); 465 | test:assertEquals(response?.scope, "read write dolphin"); 466 | test:assertEquals(response?.clientId, "l238j323ds-23ij4"); 467 | test:assertEquals(response?.username, "jdoe"); 468 | test:assertEquals(response?.tokenType, "token_type"); 469 | test:assertTrue(response?.exp is int); 470 | test:assertTrue(response?.iat is int); 471 | test:assertTrue(response?.nbf is int); 472 | test:assertEquals(response?.sub, "Z5O3upPC88QrAjx00dis"); 473 | test:assertEquals(response?.aud, "https://protected.example.net/resource"); 474 | test:assertEquals(response?.iss, "https://server.example.com/"); 475 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 476 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 477 | } 478 | 479 | @test:Config { 480 | groups: ["skipOnWindows"] 481 | } 482 | isolated function testTokenIntrospectionRequestWithSecureSocketAndWithoutUrlScheme() returns Error? { 483 | string accessToken = "56ede317-4511-44b4-8579-a08f094ee8c5"; 484 | IntrospectionConfig config = { 485 | url: "localhost:9445/oauth2/introspect", 486 | tokenTypeHint: "access_token", 487 | clientConfig: { 488 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 489 | secureSocket: { 490 | cert: { 491 | path: TRUSTSTORE_PATH, 492 | password: "ballerina" 493 | } 494 | } 495 | } 496 | }; 497 | ListenerOAuth2Provider provider = new(config); 498 | IntrospectionResponse response = check provider.authorize(accessToken, {"example_key": "example_value"}); 499 | test:assertTrue(response.active); 500 | test:assertEquals(response?.scope, "read write dolphin"); 501 | test:assertEquals(response?.clientId, "l238j323ds-23ij4"); 502 | test:assertEquals(response?.username, "jdoe"); 503 | test:assertEquals(response?.tokenType, "token_type"); 504 | test:assertTrue(response?.exp is int); 505 | test:assertTrue(response?.iat is int); 506 | test:assertTrue(response?.nbf is int); 507 | test:assertEquals(response?.sub, "Z5O3upPC88QrAjx00dis"); 508 | test:assertEquals(response?.aud, "https://protected.example.net/resource"); 509 | test:assertEquals(response?.iss, "https://server.example.com/"); 510 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 511 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 512 | } 513 | 514 | @test:Config { 515 | groups: ["skipOnWindows"] 516 | } 517 | isolated function testTokenIntrospectionRequestWithSecureSocketAndWithHttpUrlScheme() returns Error? { 518 | string accessToken = "56ede317-4511-44b4-8579-a08f094ee8c5"; 519 | IntrospectionConfig config = { 520 | url: "http://localhost:9444/oauth2/introspect", 521 | tokenTypeHint: "access_token", 522 | clientConfig: { 523 | customHeaders: {"Authorization": "Basic YWRtaW46YWRtaW4="}, 524 | secureSocket: { 525 | cert: { 526 | path: TRUSTSTORE_PATH, 527 | password: "ballerina" 528 | } 529 | } 530 | } 531 | }; 532 | ListenerOAuth2Provider provider = new(config); 533 | IntrospectionResponse response = check provider.authorize(accessToken, {"example_key": "example_value"}); 534 | test:assertTrue(response.active); 535 | test:assertEquals(response?.scope, "read write dolphin"); 536 | test:assertEquals(response?.clientId, "l238j323ds-23ij4"); 537 | test:assertEquals(response?.username, "jdoe"); 538 | test:assertEquals(response?.tokenType, "token_type"); 539 | test:assertTrue(response?.exp is int); 540 | test:assertTrue(response?.iat is int); 541 | test:assertTrue(response?.nbf is int); 542 | test:assertEquals(response?.sub, "Z5O3upPC88QrAjx00dis"); 543 | test:assertEquals(response?.aud, "https://protected.example.net/resource"); 544 | test:assertEquals(response?.iss, "https://server.example.com/"); 545 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 546 | test:assertEquals(response?.jti, "JlbmMiOiJBMTI4Q0JDLUhTMjU2In"); 547 | } 548 | 549 | // Test that the `exp` field is correctly parsed as an integer when given an example string value for `exp` field 550 | @test:Config {} 551 | isolated function testPrepareIntrospectionResponseWithStringExpClaim() { 552 | json simulatedResponse = { 553 | active: true, 554 | exp: "1672531199", 555 | scope: "read write", 556 | client_id: "test_client_id", 557 | username: "test_user" 558 | }; 559 | IntrospectionResponse response = prepareIntrospectionResponse(simulatedResponse); 560 | test:assertEquals(response.exp, 1672531199); 561 | } 562 | --------------------------------------------------------------------------------