diff --git a/examples/banking-accounts-service/Ballerina.toml b/examples/banking-accounts-service/Ballerina.toml new file mode 100644 index 000000000..c7d304307 --- /dev/null +++ b/examples/banking-accounts-service/Ballerina.toml @@ -0,0 +1,7 @@ +[package] +org = "auth" +name = "banking_account_service" +version = "1.0.0" + +[build-options] +observabilityIncluded = true diff --git a/examples/banking-accounts-service/Config.toml b/examples/banking-accounts-service/Config.toml new file mode 100644 index 000000000..71fdce92a --- /dev/null +++ b/examples/banking-accounts-service/Config.toml @@ -0,0 +1,17 @@ +[[ballerina.auth.users]] +username = "alice" +password = "alice@123" +scopes = ["read-account", "read-balance", "funds-transfer"] + +[[ballerina.auth.users]] +username = "bob" +password = "bob@123" +scopes = ["read-account", "read-balance"] + +[[ballerina.auth.users]] +username = "david" +password = "david@123" +scopes = ["read-account"] + +[ballerina.log] +level = "INFO" diff --git a/examples/banking-accounts-service/README.md b/examples/banking-accounts-service/README.md new file mode 100644 index 000000000..0b940a09c --- /dev/null +++ b/examples/banking-accounts-service/README.md @@ -0,0 +1,119 @@ +# Secured Banking Account Management Service with File Store Basic Auth with Scopes + +[![Star on Github](https://img.shields.io/badge/-Star%20on%20Github-blue?style=social&logo=github)](https://github.com/ballerina-platform/module-ballerina-auth) + +_Authors_: [@harshalkh](https://github.com/harshalkh) \ +_Reviewers_: @ThisaruGuruge @DimuthuMadushan \ +_Created_: 2023/10/16 \ +_Updated_: 2023/10/16 + +## Overview + +This guide explains how to secure the 'Banking Account Management Service' (RESTful service) with Basic Auth using File Store in Ballerina. + +The end-user (customer) in this example, Alice, Bob and David, interacts with the system using the web/mobile app provided. This web/mobile app acts as a 'Client' on behalf of the user’s actions and calls to the 'API Gateway'. The 'API Gateway' routes the requests to 'Banking Service', which is responsible for processing the requests for the customer. + +> **NOTE:** For this guide, since discussion is about the File Store based Basic Auth security aspects, focus is on the network +interactions once the 'API Gateway' receives a request. The transaction data is stored locally in [table](https://ballerina.io/learn/by-example/table/) + +- The 'API Gateway' intercepts the request from the end-user, extracts the credentials (username and password which is + concatenated with a `:` and Base64 encoded), and then talks to File Store Listener to validate the credentials. +- After validating the credentials, the 'API Gateway' talks to 'Banking Account Service' with mTLS (mutual TLS). +- The 'Banking Account Service' uses table data to process customer request based on their authorization scopes. + +## Implementation + +- You can get started with the 'API Gateway', which is responsible to authorize the requests using Basic Auth with the use of File user store and forward the request to the actual microservice via mTLS (mutual TLS). In this scenario, it is 'Banking Account Service'. The 'API Gateway' service is secured by setting the `auth` attribute of `http:ServiceConfig` with the Basic Auth - File user store configurations, so that the Ballerina HTTP service knows how to validate the credentials with the configured File user store from Config.toml. Once validated, the business logic defined inside the resource will get executed. In this case, it will call the 'Banking Account Service' via mTLS and return the response to the 'Client'. +- In addition to declarative approach for Authentication and Authorization, service uses [Imperative Approach](https://ballerina.io/spec/http/#912-imperative-approach) as service needs to have granular control on authorization of customer. For example knowing customer id of user to fetch account details, available balance before proceeding for execution of payment. + +> **NOTE:** The rest of the components such as Database Management System are not implemented as the main purpose of this article is to showcase the Basic Auth functionalities. But for the completeness of the story, the API Gateway will return a response from the data stored on in-memory tables. + +## Testing + +You can run the 'API Gateway' that we developed above, in our local environment. In order to run this service you need to setup prerequisite of Ballerina. You can refer documentation [here](https://ballerina.io/learn/get-started/) + +Now, navigate to [`examples`](../) directory and execute the following command. +```shell +$ bal run banking-accounts-service +``` + +The successful execution of the service should show us the following output. +```shell +Compiling source + auth/banking_account_service:1.0.0 + +Running executable +``` + +Now, you can test authentication and authorization checks being enforced on different actions by sending HTTP requests. +This example uses the Unit Tests to test each scenario as follows. + +#### Without authentication + +```ballerina +http:Response response = check testClient->get("/accounts/account"); +test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); +``` + +#### Authenticating as anonymous user + +```ballerina +map headers = { + "Authorization": "Basic random" +}; +http:Response response = check testClient->get("/accounts/account", headers); +test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); +``` + +#### Detailed scenarios: + +| Scenario\User | Alice | Bob | David | +| --- | --- | --- | --- | +| Scopes | `read-account` `read-balance` `funds-transfer` | `read-account` `read-balance` | `read-account` | +| Accessing `GET /accounts/account` | `200` Account Details for Alice | `200` Account Details for Bob | `200` Account Details for David | +| Accessing `GET /accounts/balance` | `200` Account Details with Balance for Alice | `200` Account Details with Balance for Bob | `403` Forbidden | +| Accessing `POST /payments/transfer` where transaction amount within available balance | `200` Response with unique paymentId and status as SUCCESS | `403` Forbidden | `403` Forbidden | +| Accessing `POST /payments/transfer` where transaction amount higher than available balance| `200` Response with unique paymentId and status as FAILED | `403` Forbidden | `403` Forbidden | + + + +## Deployment + +Once the development is done, you can deploy the service using any of the methods that are listed below. + +### Deploying Locally + +Now, you can build Ballerina executable files (.jar) of the components that we developed above. Open the terminal and +navigate to [`examples/banking-account-service`](../banking-accounts-service/), and execute the following command for +each of them. + +```shell +$ bal build +``` + +The successful execution of the above command should show us the following outputs in order. + +```shell +Compiling source + auth/api_gateway:1.0.0 + +Generating executable + target/bin/api_gateway.jar +``` + +Once the `*.jar` file is created inside the `target/bin` directories, we can run the components with the following commands in order. + +```shell +$ bal run target/bin/api_gateway.jar +``` + +### Deploying Code to Cloud + +Ballerina code to cloud supports generating the deployment artifacts of the Docker and Kubernetes. +Refer to [Code to Cloud](https://ballerina.io/learn/code-to-cloud-deployment/) guide for more information. + +## Observability + +HTTP/HTTPS based Ballerina services and any client connectors are observable by default. +[Observing Ballerina Code](https://ballerina.io/learn/observe-ballerina-programs/#provide-observability-in-ballerina) guide provides +information on enabling Ballerina service observability with some of its supported systems. diff --git a/examples/banking-accounts-service/api_gateway.bal b/examples/banking-accounts-service/api_gateway.bal new file mode 100644 index 000000000..a211b880f --- /dev/null +++ b/examples/banking-accounts-service/api_gateway.bal @@ -0,0 +1,180 @@ +// Copyright (c) 2023, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/auth; +import ballerina/http; +import ballerina/io; +import ballerina/uuid; + +type Balance record {| + string name; + string amount; + string currency; +|}; + +type AccountWithBalances record {| + string id; + string accountNumber; + readonly string customerId; + string customerName; + string productType; + string status; + Balance[] balances; +|}; + +type PaymentRequest readonly & record {| + string amount; + string currency; + string creditor; +|}; + +type PaymentResponse readonly & record {| + string id; + string status; + string failureReason?; +|}; + +table key(customerId) accountBalances = table [ + { + id: "vgshdkrokjhbbb", + accountNumber: "1234 1234 1234", + customerId: "alice", + customerName: "Alice Alice", + productType: "Savings Account", + status: "Active", + balances: [ + { name: "Available", amount: "1000", currency: "INR" }, + { name: "Ledger", amount: "1000", currency: "INR" }, + { name: "Uncleared", amount: "0", currency: "INR" } + ] + }, + { + id: "vgksurbkfldppd", + accountNumber: "1234 1234 6789", + customerId: "bob", + customerName: "Bob Bob", + productType: "Current Account", + status: "Active", + balances: [ + { name: "Available", amount: "10000", currency: "INR" }, + { name: "Ledger", amount: "1000", currency: "INR" }, + { name: "Uncleared", amount: "0", currency: "INR" } + ] + }, + { + id: "vgskspwldkdddn", + accountNumber: "1234 1234 2345", + customerId: "david", + customerName: "David David", + productType: "Savings Account", + status: "Active", + balances: [ + { name: "Available", amount: "8000", currency: "INR" }, + { name: "Ledger", amount: "1000", currency: "INR" }, + { name: "Uncleared", amount: "0", currency: "INR" } + ] + } +]; + +listener http:Listener apiGateway = new (9090, + secureSocket = { + key: { + certFile: "../banking-accounts-service/resources/public.crt", + keyFile: "../banking-accounts-service/resources/private.key" + } + } +); + +// Imperative approach as we need to know about customer authozation details for filtering data +// https://ballerina.io/spec/http/#912-imperative-approach +http:FileUserStoreConfig config = {}; +http:ListenerFileUserStoreBasicAuthHandler handler = new (config); + +@http:ServiceConfig { + auth: [ + { + fileUserStoreConfig: {}, + scopes: ["read-account"] + } + ] +} +service /accounts on apiGateway { + resource function get account(@http:Header string? Authorization) returns AccountWithBalances[] { + string customerId = getCustomerId(Authorization); + AccountWithBalances[] accountBalance = from AccountWithBalances account in accountBalances + where account.customerId == customerId + select account; + AccountWithBalances[] accountBalance1 = accountBalance.clone(); + accountBalance1[0].balances = []; + return accountBalance1; + } + + @http:ResourceConfig { + auth: [ + { + fileUserStoreConfig: {}, + scopes: ["read-balance"] + } + ] + } + resource function get balances(@http:Header string? Authorization) returns AccountWithBalances[] { + string customerId = getCustomerId(Authorization); + AccountWithBalances[] accountBalance = from AccountWithBalances account in accountBalances + where account.customerId == customerId + select account; + return accountBalance; + } +} + +@http:ServiceConfig { + auth: [ + { + fileUserStoreConfig: {}, + scopes: ["funds-transfer"] + } + ] +} +service /payments on apiGateway { + resource function post transfer(@http:Payload PaymentRequest paymentRequest, @http:Header string? Authorization) returns PaymentResponse { + string customerId = getCustomerId(Authorization); + AccountWithBalances[] accountBalance = from AccountWithBalances account in accountBalances + where account.customerId == customerId + select account; + boolean balAvailable = accountBalance[0].balances + .filter(bal => bal.name=="Available").some(bal1 => bal1.amount>=paymentRequest.amount); + if !balAvailable { + io:println("Insufficient Balance in account"); + return { + id: uuid:createType4AsString(), + status: "FAILED", + failureReason: "Insufficient Balance in account" + }; + } + return { + id: uuid:createType4AsString(), + status: "SUCCESS" + }; + } +} + +public function getCustomerId(string? authorization) returns string { + auth:UserDetails|http:Unauthorized authn = handler.authenticate(authorization is () ? "" : authorization); + string customerId = ""; + if authn is auth:UserDetails { + customerId = authn.username; + } + return customerId; +} diff --git a/examples/banking-accounts-service/resources/private.key b/examples/banking-accounts-service/resources/private.key new file mode 100644 index 000000000..fcdc5dc6f --- /dev/null +++ b/examples/banking-accounts-service/resources/private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBXKLp9WJUuJko +SfDn3HM2LWVxiHrP11me6D4X2AqoUiCZ1w976q1LuBlzWNhONYwJvHXewUuRmo0d +4NRp2ma0qjdMN246XWyozpzPfJdvovfiT+V6HnSbMg82ZppfF3e85Cgrg6mSA7Wf +faaHr62SnexseZ9ioNCuRUrW2Zg208l99IItP4K4iGclQlyjs3S6drH7JJk3rv8B +f76wmmoCEAhknOtGLq543dEUIjFgNdIy/GsSY52fiA4LSjUCv27kGn03nayqoZs3 +XHqM0qLdt9SPXbYe7l75j0o6Z9tyGFCKXY/zVwiLfRUqDIOfkN5YS4Ohit52Tj7q +oBmvEzD7AgMBAAECggEAXM/F4u23OummmQ1T1kaIMpqnaalt06jCGAywYBMUsmca +FMYDyfg5lVXkjKl1p8crTeD1AHjWawTjskgYnkmf3ocxXXF3mFBnIUX7o7HURLg7 ++RcxoUgwiRiFaZZ7szX3JoLbfzzbcHNQ37kavccBVWwQsFMiU3Tlw+LbKwK6/row +LYsQPx7gT4u7hViat4vQDTYcgyjvvFCiek4ndL6O9K49MxIMU678UXB6ia5iUevy +vgEfcYkKQ5EQ38qS3ZwsubPvj4633jvAJRr/hJD8XINZC74kTXeV3BGH2LlpQOEq +kWkOypwYNjnXtt1JO8+Iu6mEXKUoiIBPfGrJ3vDSQQKBgQDmYPc7kfYan/LHjJRv +iE2CwbC26yVA6+BEPQv9z7jChO9Q6cUbGvM8EEVNpC9nmFogkslzJhz55HP84QZL +u3ptU+D96ncq6zkBqxBfRnZG++D36+XRXIwzz3h+g1Nwrl0y0MFbwlkMm3ZqJdd6 +pZz1FZGd6zvQftW8m7jPSKHuswKBgQCPv6czFOZR6bI+qCQdaORpe9JGoAduOD+4 +YKl96s0eiAKhkGhFCrMd6GJwWRkpNcfwB+J9sMahORbfvwiYanI56h7Vi30DFPRb +m1m8dLkr6z+8bxMxKJaMXIIjy3UDamgDr7QHInNUih2iGvtB8QqZ0aobsB2XIxZg +qESTMcpYmQKBgHSwSqneraQgvgz7FLhFdtUzHDoacr0mfGqz7R37F99XDAyUy+SF +ywvyRdgkwGodjhEPqH/tnyGn6GP+6nxzknhL0xtppkCT8kT5C4rmmsQrknChCL/5 +u34GqUaTaDEb8FLrz/SVRRuQpvLvBey2dADjkuVFH//kLoig64P6iyLnAoGBAIlF +g+2L78YZXVXoS1SqbjUtQUigWXgvzunLpQ/Rwb9+MsUGmgwUg6fz2s1eyGBKM3xM +i0VsIsKjOezBCPxD6oDTyk4yvlbLE+7HE5KcBJikNmFD0RgIonu3e6+jA0MXweyD +RW/qviflHRdInNgDzxPE3KVEMX26zAvRpGrMCWdBAoGAdQ5SvX+mAC3cKqoQ9Zal +lSqWoyjfzP5EaVRG8dtoLxbznQGTTvtHXc65/MznX/L9qkWCS6Eb4HH5M3hFNY46 +LNIzGQLznE1odwv7H5B8c0/m3DrKTxbh8bYcrR1BW5/nKZNNW7k1O6OjEozvAajK +JQdp3KBU9S8CmBjGrRpJ2qw= +-----END PRIVATE KEY----- diff --git a/examples/banking-accounts-service/resources/public.crt b/examples/banking-accounts-service/resources/public.crt new file mode 100644 index 000000000..77f917439 --- /dev/null +++ b/examples/banking-accounts-service/resources/public.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEfP3e8zANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV +UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxDTALBgNVBAoT +BFdTTzIxDTALBgNVBAsTBFdTTzIxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEw +MjQwNTQ3NThaFw0zNzEwMTkwNTQ3NThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI +EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzENMAsGA1UEChMEV1NPMjENMAsG +A1UECxMEV1NPMjESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAgVyi6fViVLiZKEnw59xzNi1lcYh6z9dZnug+F9gKqFIg +mdcPe+qtS7gZc1jYTjWMCbx13sFLkZqNHeDUadpmtKo3TDduOl1sqM6cz3yXb6L3 +4k/leh50mzIPNmaaXxd3vOQoK4OpkgO1n32mh6+tkp3sbHmfYqDQrkVK1tmYNtPJ +ffSCLT+CuIhnJUJco7N0unax+ySZN67/AX++sJpqAhAIZJzrRi6ueN3RFCIxYDXS +MvxrEmOdn4gOC0o1Ar9u5Bp9N52sqqGbN1x6jNKi3bfUj122Hu5e+Y9KOmfbchhQ +il2P81cIi30VKgyDn5DeWEuDoYredk4+6qAZrxMw+wIDAQABozEwLzAOBgNVHQ8B +Af8EBAMCBaAwHQYDVR0OBBYEFNmtrQ36j6tUGhKrfW9qWWE7KFzMMA0GCSqGSIb3 +DQEBCwUAA4IBAQAv3yOwgbtOu76eJMl1BCcgTFgaMUBZoUjK9Un6HGjKEgYz/YWS +ZFlY/qH5rT01DWQevUZB626d5ZNdzSBZRlpsxbf9IE/ursNHwHx9ua6fB7yHUCzC +1ZMp1lvBHABi7wcA+5nbV6zQ7HDmBXFhJfbgH1iVmA1KcvDeBPSJ/scRGasZ5q2W +3IenDNrfPIUhD74tFiCiqNJO91qD/LO+++3XeZzfPh8NRKkiPX7dB8WJ3YNBuQAv +gRWTISpSSXLmqMb+7MPQVgecsepZdk8CwkRLxh3RKPJMjigmCgyvkSaoDMKAYC3i +YjfUTiJ57UeqoSl0IaOFJ0wfZRFh+UytlDZa +-----END CERTIFICATE----- diff --git a/examples/banking-accounts-service/tests/Config.toml b/examples/banking-accounts-service/tests/Config.toml new file mode 100644 index 000000000..71fdce92a --- /dev/null +++ b/examples/banking-accounts-service/tests/Config.toml @@ -0,0 +1,17 @@ +[[ballerina.auth.users]] +username = "alice" +password = "alice@123" +scopes = ["read-account", "read-balance", "funds-transfer"] + +[[ballerina.auth.users]] +username = "bob" +password = "bob@123" +scopes = ["read-account", "read-balance"] + +[[ballerina.auth.users]] +username = "david" +password = "david@123" +scopes = ["read-account"] + +[ballerina.log] +level = "INFO" diff --git a/examples/banking-accounts-service/tests/api_gateway_test.bal b/examples/banking-accounts-service/tests/api_gateway_test.bal new file mode 100644 index 000000000..183946f77 --- /dev/null +++ b/examples/banking-accounts-service/tests/api_gateway_test.bal @@ -0,0 +1,107 @@ +import ballerina/test; +import ballerina/http; + +http:Client testClient = check new ("https://localhost:9090", + secureSocket= { + cert: "../banking-accounts-service/resources/public.crt" + } +); + +@test:Config {} +public function testRequestsWithoutAuthorizationHeader() returns error? { + //Request without authorization header + http:Response response = check testClient->get("/accounts/account"); + test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); + response = check testClient->get("/accounts/balances"); + test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); + response = check testClient->post("/payments/transfer", { amount: "100", currency: "INR", creditor: "bob" }); + test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); +} + +@test:Config {} +public function testRequestsWithInvalidAuthorizationHeader() returns error? { + //Request with invalid authorization header + map headers = { + "Authorization": "Basic random" + }; + http:Response response = check testClient->get("/accounts/account", headers); + test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); + response = check testClient->get("/accounts/balances", headers); + test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); + response = check testClient->post("/payments/transfer", { amount: "100", currency: "INR", creditor: "bob" }, headers); + test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); +} + +//Test for user alice, scopes = read-account, read-balance, funds-transfer +@test:Config {} +public function testRequestsWithUserHavingAuthorizationOfAllScopes() returns error? { + //Request with correct authorization header + map headers = { + "Authorization": "Basic YWxpY2U6YWxpY2VAMTIz" + }; + //User has Authorization for scope read-account + AccountWithBalances[] accountsAlice = check testClient->get("/accounts/account", headers); + test:assertEquals(accountsAlice, getExpectedAccounts("alice")); + //User has Authorization for scope read-balance + AccountWithBalances[] accountsWithBalanceAlice = check testClient->get("/accounts/balances", headers); + test:assertEquals(accountsWithBalanceAlice, getExpectedAccountsWithBalance("alice")); + //User has Authorization for scope funds-transfer + PaymentResponse paymentResponse = check testClient->post("/payments/transfer", { amount: "100", currency: "INR", creditor: "bob" }, headers); + test:assertEquals(paymentResponse.status, "SUCCESS"); + //User tried to do transfer for amount more than available balance + paymentResponse = check testClient->post("/payments/transfer", { amount: "1001", currency: "INR", creditor: "bob" }, headers); + test:assertEquals(paymentResponse.status, "FAILED"); + test:assertEquals(paymentResponse.failureReason, "Insufficient Balance in account"); +} + +//Test for user bob, scopes = read-account, read-balance +@test:Config {} +public function testRequestsWithUserHavingAuthorizationOfFewScopes() returns error? { + //Request with correct authorization header + map headers = { + "Authorization": "Basic Ym9iOmJvYkAxMjM=" + }; + //User has Authorization for scope read-account + AccountWithBalances[] accountsBob = check testClient->get("/accounts/account", headers); + test:assertEquals(accountsBob, getExpectedAccounts("bob")); + //User has Authorization for scope read-balance + AccountWithBalances[] accountsWithBalanceBob = check testClient->get("/accounts/balances", headers); + test:assertEquals(accountsWithBalanceBob, getExpectedAccountsWithBalance("bob")); + //User does not have Authorization for scope funds-transfer + http:Response response = check testClient->post("/payments/transfer", { amount: "1001", currency: "INR", creditor: "bob" }, headers); + test:assertEquals(response.statusCode, http:STATUS_FORBIDDEN); +} + +//Test for User david, scopes = read-account +@test:Config {} +public function testRequestsWithUserHavingAuthorizationOfOnlyOneScope() returns error? { + //Request with correct authorization header + map headers = { + "Authorization": "Basic ZGF2aWQ6ZGF2aWRAMTIz" + }; + //User has Authorization for scope read-account + AccountWithBalances[] accountsDavid = check testClient->get("/accounts/account", headers); + test:assertEquals(accountsDavid, getExpectedAccounts("david")); + //User does not have Authorization for scope read-balance + http:Response response = check testClient->get("/accounts/balances"); + test:assertEquals(response.statusCode, http:STATUS_UNAUTHORIZED); + //User does not have Authorization for scope funds-transfer + response = check testClient->post("/payments/transfer", { amount: "100", currency: "INR", creditor: "bob" }, headers); + test:assertEquals(response.statusCode, http:STATUS_FORBIDDEN); +} + +public function getExpectedAccounts(string customerId) returns AccountWithBalances[] { + AccountWithBalances[] accountBalance = accountBalances + .filter(acc => acc.customerId == customerId) + .toArray(); + AccountWithBalances[] accountBalance1 = accountBalance.clone(); + accountBalance1[0].balances = []; + return accountBalance1; +} + +public function getExpectedAccountsWithBalance(string customerId) returns AccountWithBalances[] { + AccountWithBalances[] accountBalance = accountBalances + .filter(acc => acc.customerId == customerId) + .toArray(); + return accountBalance; +} diff --git a/examples/banking-accounts-service/tests/resources/private.key b/examples/banking-accounts-service/tests/resources/private.key new file mode 100644 index 000000000..fcdc5dc6f --- /dev/null +++ b/examples/banking-accounts-service/tests/resources/private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBXKLp9WJUuJko +SfDn3HM2LWVxiHrP11me6D4X2AqoUiCZ1w976q1LuBlzWNhONYwJvHXewUuRmo0d +4NRp2ma0qjdMN246XWyozpzPfJdvovfiT+V6HnSbMg82ZppfF3e85Cgrg6mSA7Wf +faaHr62SnexseZ9ioNCuRUrW2Zg208l99IItP4K4iGclQlyjs3S6drH7JJk3rv8B +f76wmmoCEAhknOtGLq543dEUIjFgNdIy/GsSY52fiA4LSjUCv27kGn03nayqoZs3 +XHqM0qLdt9SPXbYe7l75j0o6Z9tyGFCKXY/zVwiLfRUqDIOfkN5YS4Ohit52Tj7q +oBmvEzD7AgMBAAECggEAXM/F4u23OummmQ1T1kaIMpqnaalt06jCGAywYBMUsmca +FMYDyfg5lVXkjKl1p8crTeD1AHjWawTjskgYnkmf3ocxXXF3mFBnIUX7o7HURLg7 ++RcxoUgwiRiFaZZ7szX3JoLbfzzbcHNQ37kavccBVWwQsFMiU3Tlw+LbKwK6/row +LYsQPx7gT4u7hViat4vQDTYcgyjvvFCiek4ndL6O9K49MxIMU678UXB6ia5iUevy +vgEfcYkKQ5EQ38qS3ZwsubPvj4633jvAJRr/hJD8XINZC74kTXeV3BGH2LlpQOEq +kWkOypwYNjnXtt1JO8+Iu6mEXKUoiIBPfGrJ3vDSQQKBgQDmYPc7kfYan/LHjJRv +iE2CwbC26yVA6+BEPQv9z7jChO9Q6cUbGvM8EEVNpC9nmFogkslzJhz55HP84QZL +u3ptU+D96ncq6zkBqxBfRnZG++D36+XRXIwzz3h+g1Nwrl0y0MFbwlkMm3ZqJdd6 +pZz1FZGd6zvQftW8m7jPSKHuswKBgQCPv6czFOZR6bI+qCQdaORpe9JGoAduOD+4 +YKl96s0eiAKhkGhFCrMd6GJwWRkpNcfwB+J9sMahORbfvwiYanI56h7Vi30DFPRb +m1m8dLkr6z+8bxMxKJaMXIIjy3UDamgDr7QHInNUih2iGvtB8QqZ0aobsB2XIxZg +qESTMcpYmQKBgHSwSqneraQgvgz7FLhFdtUzHDoacr0mfGqz7R37F99XDAyUy+SF +ywvyRdgkwGodjhEPqH/tnyGn6GP+6nxzknhL0xtppkCT8kT5C4rmmsQrknChCL/5 +u34GqUaTaDEb8FLrz/SVRRuQpvLvBey2dADjkuVFH//kLoig64P6iyLnAoGBAIlF +g+2L78YZXVXoS1SqbjUtQUigWXgvzunLpQ/Rwb9+MsUGmgwUg6fz2s1eyGBKM3xM +i0VsIsKjOezBCPxD6oDTyk4yvlbLE+7HE5KcBJikNmFD0RgIonu3e6+jA0MXweyD +RW/qviflHRdInNgDzxPE3KVEMX26zAvRpGrMCWdBAoGAdQ5SvX+mAC3cKqoQ9Zal +lSqWoyjfzP5EaVRG8dtoLxbznQGTTvtHXc65/MznX/L9qkWCS6Eb4HH5M3hFNY46 +LNIzGQLznE1odwv7H5B8c0/m3DrKTxbh8bYcrR1BW5/nKZNNW7k1O6OjEozvAajK +JQdp3KBU9S8CmBjGrRpJ2qw= +-----END PRIVATE KEY----- diff --git a/examples/banking-accounts-service/tests/resources/public.crt b/examples/banking-accounts-service/tests/resources/public.crt new file mode 100644 index 000000000..77f917439 --- /dev/null +++ b/examples/banking-accounts-service/tests/resources/public.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEfP3e8zANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV +UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxDTALBgNVBAoT +BFdTTzIxDTALBgNVBAsTBFdTTzIxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEw +MjQwNTQ3NThaFw0zNzEwMTkwNTQ3NThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI +EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzENMAsGA1UEChMEV1NPMjENMAsG +A1UECxMEV1NPMjESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAgVyi6fViVLiZKEnw59xzNi1lcYh6z9dZnug+F9gKqFIg +mdcPe+qtS7gZc1jYTjWMCbx13sFLkZqNHeDUadpmtKo3TDduOl1sqM6cz3yXb6L3 +4k/leh50mzIPNmaaXxd3vOQoK4OpkgO1n32mh6+tkp3sbHmfYqDQrkVK1tmYNtPJ +ffSCLT+CuIhnJUJco7N0unax+ySZN67/AX++sJpqAhAIZJzrRi6ueN3RFCIxYDXS +MvxrEmOdn4gOC0o1Ar9u5Bp9N52sqqGbN1x6jNKi3bfUj122Hu5e+Y9KOmfbchhQ +il2P81cIi30VKgyDn5DeWEuDoYredk4+6qAZrxMw+wIDAQABozEwLzAOBgNVHQ8B +Af8EBAMCBaAwHQYDVR0OBBYEFNmtrQ36j6tUGhKrfW9qWWE7KFzMMA0GCSqGSIb3 +DQEBCwUAA4IBAQAv3yOwgbtOu76eJMl1BCcgTFgaMUBZoUjK9Un6HGjKEgYz/YWS +ZFlY/qH5rT01DWQevUZB626d5ZNdzSBZRlpsxbf9IE/ursNHwHx9ua6fB7yHUCzC +1ZMp1lvBHABi7wcA+5nbV6zQ7HDmBXFhJfbgH1iVmA1KcvDeBPSJ/scRGasZ5q2W +3IenDNrfPIUhD74tFiCiqNJO91qD/LO+++3XeZzfPh8NRKkiPX7dB8WJ3YNBuQAv +gRWTISpSSXLmqMb+7MPQVgecsepZdk8CwkRLxh3RKPJMjigmCgyvkSaoDMKAYC3i +YjfUTiJ57UeqoSl0IaOFJ0wfZRFh+UytlDZa +-----END CERTIFICATE-----