mirror of
https://github.com/OpenBankProject/OBP-API.git
synced 2026-02-06 11:06:49 +00:00
Merge pull request #2658 from simonredfern/develop
Various New Features
This commit is contained in:
commit
efb2d6e7b4
35
.github/workflows/auto_update_base_image.yml
vendored
35
.github/workflows/auto_update_base_image.yml
vendored
@ -1,35 +0,0 @@
|
||||
name: Regular base image update check
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 5 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
## Sets environment variable
|
||||
DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker Image Update Checker
|
||||
id: baseupdatecheck
|
||||
uses: lucacome/docker-image-update-checker@v2.0.0
|
||||
with:
|
||||
base-image: jetty:9.4-jdk11-alpine
|
||||
image: ${{ env.DOCKER_HUB_ORGANIZATION }}/obp-api:latest
|
||||
|
||||
- name: Trigger build_container_develop_branch workflow
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
workflow_id: 'build_container_develop_branch.yml',
|
||||
ref: 'refs/heads/develop'
|
||||
});
|
||||
if: steps.baseupdatecheck.outputs.needs-updating == 'true'
|
||||
@ -124,33 +124,3 @@ jobs:
|
||||
with:
|
||||
name: ${{ github.sha }}
|
||||
path: push/
|
||||
|
||||
- name: Build the Docker image
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io
|
||||
docker build . --file .github/Dockerfile_PreBuild --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:latest --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:develop
|
||||
docker build . --file .github/Dockerfile_PreBuild_OC --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA-OC --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:latest-OC --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:develop-OC --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:${GITHUB_REF##*/}-OC
|
||||
docker push docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }} --all-tags
|
||||
echo docker done
|
||||
|
||||
- uses: sigstore/cosign-installer@4d14d7f17e7112af04ea6108fbb4bfc714c00390
|
||||
|
||||
- name: Write signing key to disk (only needed for `cosign sign --key`)
|
||||
run: echo "${{ secrets.COSIGN_PRIVATE_KEY }}" > cosign.key
|
||||
|
||||
- name: Sign container image
|
||||
run: |
|
||||
cosign sign -y --key cosign.key \
|
||||
docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:develop
|
||||
cosign sign -y --key cosign.key \
|
||||
docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:latest
|
||||
cosign sign -y --key cosign.key \
|
||||
docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA
|
||||
cosign sign -y --key cosign.key \
|
||||
docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:develop-OC
|
||||
cosign sign -y --key cosign.key \
|
||||
docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:latest-OC
|
||||
env:
|
||||
COSIGN_PASSWORD: "${{secrets.COSIGN_PASSWORD}}"
|
||||
|
||||
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
name: Build and publish container non develop
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!develop'
|
||||
|
||||
env:
|
||||
DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }}
|
||||
DOCKER_HUB_REPOSITORY: obp-api
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
# Label used to access the service container
|
||||
redis:
|
||||
# Docker Hub image
|
||||
image: redis
|
||||
ports:
|
||||
# Opens tcp port 6379 on the host and service container
|
||||
- 6379:6379
|
||||
# Set health checks to wait until redis has started
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Extract branch name
|
||||
shell: bash
|
||||
run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: maven
|
||||
- name: Build with Maven
|
||||
run: |
|
||||
set -o pipefail
|
||||
cp obp-api/src/main/resources/props/sample.props.template obp-api/src/main/resources/props/production.default.props
|
||||
echo connector=star > obp-api/src/main/resources/props/test.default.props
|
||||
echo starConnector_supported_types=mapped,internal >> obp-api/src/main/resources/props/test.default.props
|
||||
echo hostname=http://localhost:8016 >> obp-api/src/main/resources/props/test.default.props
|
||||
echo tests.port=8016 >> obp-api/src/main/resources/props/test.default.props
|
||||
echo End of minimum settings >> obp-api/src/main/resources/props/test.default.props
|
||||
echo payments_enabled=false >> obp-api/src/main/resources/props/test.default.props
|
||||
echo importer_secret=change_me >> obp-api/src/main/resources/props/test.default.props
|
||||
echo messageQueue.updateBankAccountsTransaction=false >> obp-api/src/main/resources/props/test.default.props
|
||||
echo messageQueue.createBankAccounts=false >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allow_sandbox_account_creation=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allow_sandbox_data_import=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo sandbox_data_import_secret=change_me >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allow_account_deletion=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allowed_internal_redirect_urls = /,/oauth/authorize >> obp-api/src/main/resources/props/test.default.props
|
||||
echo transactionRequests_enabled=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo transactionRequests_supported_types=SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,SIMPLE >> obp-api/src/main/resources/props/test.default.props
|
||||
echo SIMPLE_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo openredirects.hostname.whitlelist=http://127.0.0.1,http://localhost >> obp-api/src/main/resources/props/test.default.props
|
||||
echo remotedata.secret = foobarbaz >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allow_public_views=true >> obp-api/src/main/resources/props/test.default.props
|
||||
|
||||
echo SIMPLE_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo ACCOUNT_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo SEPA_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo FREE_FORM_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo SEPA_CREDIT_TRANSFERS_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
|
||||
echo allow_oauth2_login=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo oauth2.jwk_set.url=https://www.googleapis.com/oauth2/v3/certs >> obp-api/src/main/resources/props/test.default.props
|
||||
|
||||
echo ResetPasswordUrlEnabled=true >> obp-api/src/main/resources/props/test.default.props
|
||||
|
||||
echo consents.allowed=true >> obp-api/src/main/resources/props/test.default.props
|
||||
MAVEN_OPTS="-Xmx3G -Xss2m" mvn clean package -Pprod 2>&1 | tee maven-build.log
|
||||
|
||||
- name: Report failing tests (if any)
|
||||
if: always()
|
||||
run: |
|
||||
echo "Checking build log for failing tests via grep..."
|
||||
if [ ! -f maven-build.log ]; then
|
||||
echo "No maven-build.log found; skipping failure scan."
|
||||
exit 0
|
||||
fi
|
||||
if grep -n "\*\*\* FAILED \*\*\*" maven-build.log; then
|
||||
echo "Failing tests detected above."
|
||||
exit 1
|
||||
else
|
||||
echo "No failing tests detected in maven-build.log."
|
||||
fi
|
||||
|
||||
- name: Upload Maven build log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: maven-build-log
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
maven-build.log
|
||||
|
||||
- name: Upload test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
obp-api/target/surefire-reports/**
|
||||
obp-commons/target/surefire-reports/**
|
||||
**/target/scalatest-reports/**
|
||||
**/target/site/surefire-report.html
|
||||
**/target/site/surefire-report/*
|
||||
|
||||
- name: Save .war artifact
|
||||
run: |
|
||||
mkdir -p ./push
|
||||
cp obp-api/target/obp-api-1.*.war ./push/
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.sha }}
|
||||
path: push/
|
||||
|
||||
- name: Build the Docker image
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin docker.io
|
||||
docker build . --file .github/Dockerfile_PreBuild --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:${GITHUB_REF##*/}
|
||||
docker build . --file .github/Dockerfile_PreBuild_OC --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA-OC --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:${GITHUB_REF##*/}-OC
|
||||
docker push docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }} --all-tags
|
||||
echo docker done
|
||||
|
||||
- uses: sigstore/cosign-installer@4d14d7f17e7112af04ea6108fbb4bfc714c00390
|
||||
|
||||
- name: Write signing key to disk (only needed for `cosign sign --key`)
|
||||
run: echo "${{ secrets.COSIGN_PRIVATE_KEY }}" > cosign.key
|
||||
|
||||
- name: Sign container image
|
||||
run: |
|
||||
cosign sign -y --key cosign.key \
|
||||
docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:${GITHUB_REF##*/}
|
||||
cosign sign -y --key cosign.key \
|
||||
docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:${GITHUB_REF##*/}-OC
|
||||
cosign sign -y --key cosign.key \
|
||||
docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:$GITHUB_SHA
|
||||
env:
|
||||
COSIGN_PASSWORD: "${{secrets.COSIGN_PASSWORD}}"
|
||||
|
||||
|
||||
124
.github/workflows/build_pull_request.yml
vendored
124
.github/workflows/build_pull_request.yml
vendored
@ -1,124 +0,0 @@
|
||||
name: Build on Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
env:
|
||||
## Sets environment variable
|
||||
DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }}
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
# Label used to access the service container
|
||||
redis:
|
||||
# Docker Hub image
|
||||
image: redis
|
||||
ports:
|
||||
# Opens tcp port 6379 on the host and service container
|
||||
- 6379:6379
|
||||
# Set health checks to wait until redis has started
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: maven
|
||||
- name: Build with Maven
|
||||
run: |
|
||||
set -o pipefail
|
||||
cp obp-api/src/main/resources/props/sample.props.template obp-api/src/main/resources/props/production.default.props
|
||||
echo connector=star > obp-api/src/main/resources/props/test.default.props
|
||||
echo starConnector_supported_types=mapped,internal >> obp-api/src/main/resources/props/test.default.props
|
||||
echo hostname=http://localhost:8016 >> obp-api/src/main/resources/props/test.default.props
|
||||
echo tests.port=8016 >> obp-api/src/main/resources/props/test.default.props
|
||||
echo End of minimum settings >> obp-api/src/main/resources/props/test.default.props
|
||||
echo payments_enabled=false >> obp-api/src/main/resources/props/test.default.props
|
||||
echo importer_secret=change_me >> obp-api/src/main/resources/props/test.default.props
|
||||
echo messageQueue.updateBankAccountsTransaction=false >> obp-api/src/main/resources/props/test.default.props
|
||||
echo messageQueue.createBankAccounts=false >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allow_sandbox_account_creation=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allow_sandbox_data_import=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo sandbox_data_import_secret=change_me >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allow_account_deletion=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allowed_internal_redirect_urls = /,/oauth/authorize >> obp-api/src/main/resources/props/test.default.props
|
||||
echo transactionRequests_enabled=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo transactionRequests_supported_types=SEPA,SANDBOX_TAN,FREE_FORM,COUNTERPARTY,ACCOUNT,SIMPLE >> obp-api/src/main/resources/props/test.default.props
|
||||
echo SIMPLE_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo openredirects.hostname.whitlelist=http://127.0.0.1,http://localhost >> obp-api/src/main/resources/props/test.default.props
|
||||
echo remotedata.secret = foobarbaz >> obp-api/src/main/resources/props/test.default.props
|
||||
echo allow_public_views=true >> obp-api/src/main/resources/props/test.default.props
|
||||
|
||||
echo SIMPLE_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo ACCOUNT_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo SEPA_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo FREE_FORM_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo COUNTERPARTY_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
echo SEPA_CREDIT_TRANSFERS_OTP_INSTRUCTION_TRANSPORT=dummy >> obp-api/src/main/resources/props/test.default.props
|
||||
|
||||
echo allow_oauth2_login=true >> obp-api/src/main/resources/props/test.default.props
|
||||
echo oauth2.jwk_set.url=https://www.googleapis.com/oauth2/v3/certs >> obp-api/src/main/resources/props/test.default.props
|
||||
|
||||
echo ResetPasswordUrlEnabled=true >> obp-api/src/main/resources/props/test.default.props
|
||||
|
||||
echo consents.allowed=true >> obp-api/src/main/resources/props/test.default.props
|
||||
MAVEN_OPTS="-Xmx3G -Xss2m" mvn clean package -Pprod 2>&1 | tee maven-build.log
|
||||
|
||||
- name: Report failing tests (if any)
|
||||
if: always()
|
||||
run: |
|
||||
echo "Checking build log for failing tests via grep..."
|
||||
if [ ! -f maven-build.log ]; then
|
||||
echo "No maven-build.log found; skipping failure scan."
|
||||
exit 0
|
||||
fi
|
||||
if grep -n "\*\*\* FAILED \*\*\*" maven-build.log; then
|
||||
echo "Failing tests detected above."
|
||||
exit 1
|
||||
else
|
||||
echo "No failing tests detected in maven-build.log."
|
||||
fi
|
||||
|
||||
- name: Upload Maven build log
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: maven-build-log
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
maven-build.log
|
||||
|
||||
- name: Upload test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
obp-api/target/surefire-reports/**
|
||||
obp-commons/target/surefire-reports/**
|
||||
**/target/scalatest-reports/**
|
||||
**/target/site/surefire-report.html
|
||||
**/target/site/surefire-report/*
|
||||
|
||||
- name: Save .war artifact
|
||||
run: |
|
||||
mkdir -p ./pull
|
||||
cp obp-api/target/obp-api-1.*.war ./pull/
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ github.sha }}
|
||||
path: pull/
|
||||
|
||||
|
||||
|
||||
54
.github/workflows/run_trivy.yml
vendored
54
.github/workflows/run_trivy.yml
vendored
@ -1,54 +0,0 @@
|
||||
name: scan container image
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Build and publish container develop
|
||||
- Build and publish container non develop
|
||||
types:
|
||||
- completed
|
||||
env:
|
||||
## Sets environment variable
|
||||
DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }}
|
||||
DOCKER_HUB_REPOSITORY: obp-api
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- id: trivy-db
|
||||
name: Check trivy db sha
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
endpoint='/orgs/aquasecurity/packages/container/trivy-db/versions'
|
||||
headers='Accept: application/vnd.github+json'
|
||||
jqFilter='.[] | select(.metadata.container.tags[] | contains("latest")) | .name | sub("sha256:";"")'
|
||||
sha=$(gh api -H "${headers}" "${endpoint}" | jq --raw-output "${jqFilter}")
|
||||
echo "Trivy DB sha256:${sha}"
|
||||
echo "::set-output name=sha::${sha}"
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: .trivy
|
||||
key: ${{ runner.os }}-trivy-db-${{ steps.trivy-db.outputs.sha }}
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@master
|
||||
with:
|
||||
image-ref: 'docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }}:${{ github.sha }}'
|
||||
format: 'template'
|
||||
template: '@/contrib/sarif.tpl'
|
||||
output: 'trivy-results.sarif'
|
||||
security-checks: 'vuln'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
timeout: '30m'
|
||||
cache-dir: .trivy
|
||||
- name: Fix .trivy permissions
|
||||
run: sudo chown -R $(stat . -c %u:%g) .trivy
|
||||
- name: Upload Trivy scan results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
@ -4,6 +4,7 @@ import code.api.util.{APIUtil, CallContext, DynamicUtil}
|
||||
import code.bankconnectors.Connector
|
||||
import code.model.dataAccess.ResourceUser
|
||||
import code.users.Users
|
||||
import code.entitlement.Entitlement
|
||||
import com.openbankproject.commons.model._
|
||||
import com.openbankproject.commons.ExecutionContext.Implicits.global
|
||||
import net.liftweb.common.{Box, Empty, Failure, Full}
|
||||
@ -26,12 +27,12 @@ object AbacRuleEngine {
|
||||
|
||||
/**
|
||||
* Type alias for compiled ABAC rule function
|
||||
* Parameters: authenticatedUser (logged in), authenticatedUserAttributes (non-personal), authenticatedUserAuthContext (auth context),
|
||||
* onBehalfOfUser (delegation), onBehalfOfUserAttributes, onBehalfOfUserAuthContext,
|
||||
* Parameters: authenticatedUser (logged in), authenticatedUserAttributes (non-personal), authenticatedUserAuthContext (auth context), authenticatedUserEntitlements (roles),
|
||||
* onBehalfOfUser (delegation), onBehalfOfUserAttributes, onBehalfOfUserAuthContext, onBehalfOfUserEntitlements,
|
||||
* user, userAttributes, bankOpt, bankAttributes, accountOpt, accountAttributes, transactionOpt, transactionAttributes, customerOpt, customerAttributes
|
||||
* Returns: Boolean (true = allow access, false = deny access)
|
||||
*/
|
||||
type AbacRuleFunction = (User, List[UserAttributeTrait], List[UserAuthContext], Option[User], List[UserAttributeTrait], List[UserAuthContext], Option[User], List[UserAttributeTrait], Option[Bank], List[BankAttributeTrait], Option[BankAccount], List[AccountAttribute], Option[Transaction], List[TransactionAttribute], Option[TransactionRequest], List[TransactionRequestAttributeTrait], Option[Customer], List[CustomerAttribute], Option[CallContext]) => Boolean
|
||||
type AbacRuleFunction = (User, List[UserAttributeTrait], List[UserAuthContext], List[Entitlement], Option[User], List[UserAttributeTrait], List[UserAuthContext], List[Entitlement], Option[User], List[UserAttributeTrait], Option[Bank], List[BankAttributeTrait], Option[BankAccount], List[AccountAttribute], Option[Transaction], List[TransactionAttribute], Option[TransactionRequest], List[TransactionRequestAttributeTrait], Option[Customer], List[CustomerAttribute], Option[CallContext]) => Boolean
|
||||
|
||||
/**
|
||||
* Compile an ABAC rule from Scala code
|
||||
@ -73,9 +74,11 @@ object AbacRuleEngine {
|
||||
|import com.openbankproject.commons.model._
|
||||
|import code.model.dataAccess.ResourceUser
|
||||
|import net.liftweb.common._
|
||||
|import code.entitlement.Entitlement
|
||||
|import code.api.util.CallContext
|
||||
|
|
||||
|// ABAC Rule Function
|
||||
|(authenticatedUser: User, authenticatedUserAttributes: List[UserAttributeTrait], authenticatedUserAuthContext: List[UserAuthContext], onBehalfOfUserOpt: Option[User], onBehalfOfUserAttributes: List[UserAttributeTrait], onBehalfOfUserAuthContext: List[UserAuthContext], userOpt: Option[User], userAttributes: List[UserAttributeTrait], bankOpt: Option[Bank], bankAttributes: List[BankAttributeTrait], accountOpt: Option[BankAccount], accountAttributes: List[AccountAttribute], transactionOpt: Option[Transaction], transactionAttributes: List[TransactionAttribute], transactionRequestOpt: Option[TransactionRequest], transactionRequestAttributes: List[TransactionRequestAttributeTrait], customerOpt: Option[Customer], customerAttributes: List[CustomerAttribute], callContext: Option[code.api.util.CallContext]) => {
|
||||
|(authenticatedUser: User, authenticatedUserAttributes: List[UserAttributeTrait], authenticatedUserAuthContext: List[UserAuthContext], authenticatedUserEntitlements: List[Entitlement], onBehalfOfUserOpt: Option[User], onBehalfOfUserAttributes: List[UserAttributeTrait], onBehalfOfUserAuthContext: List[UserAuthContext], onBehalfOfUserEntitlements: List[Entitlement], userOpt: Option[User], userAttributes: List[UserAttributeTrait], bankOpt: Option[Bank], bankAttributes: List[BankAttributeTrait], accountOpt: Option[BankAccount], accountAttributes: List[AccountAttribute], transactionOpt: Option[Transaction], transactionAttributes: List[TransactionAttribute], transactionRequestOpt: Option[TransactionRequest], transactionRequestAttributes: List[TransactionRequestAttributeTrait], customerOpt: Option[Customer], customerAttributes: List[CustomerAttribute], callContext: Option[code.api.util.CallContext]) => {
|
||||
| $ruleCode
|
||||
|}
|
||||
|""".stripMargin
|
||||
@ -129,6 +132,12 @@ object AbacRuleEngine {
|
||||
5.seconds
|
||||
)
|
||||
|
||||
// Fetch entitlements for authenticated user
|
||||
authenticatedUserEntitlements = Await.result(
|
||||
code.api.util.NewStyle.function.getEntitlementsByUserId(authenticatedUserId, Some(callContext)),
|
||||
5.seconds
|
||||
)
|
||||
|
||||
// Fetch onBehalfOf user if provided (delegation scenario)
|
||||
onBehalfOfUserOpt <- onBehalfOfUserId match {
|
||||
case Some(obUserId) => Users.users.vend.getUserByUserId(obUserId).map(Some(_))
|
||||
@ -155,6 +164,16 @@ object AbacRuleEngine {
|
||||
case None => List.empty[UserAuthContext]
|
||||
}
|
||||
|
||||
// Fetch entitlements for onBehalfOf user if provided
|
||||
onBehalfOfUserEntitlements = onBehalfOfUserId match {
|
||||
case Some(obUserId) =>
|
||||
Await.result(
|
||||
code.api.util.NewStyle.function.getEntitlementsByUserId(obUserId, Some(callContext)),
|
||||
5.seconds
|
||||
)
|
||||
case None => List.empty[Entitlement]
|
||||
}
|
||||
|
||||
// Fetch target user if userId is provided
|
||||
userOpt <- userId match {
|
||||
case Some(uId) => Users.users.vend.getUserByUserId(uId).map(Some(_))
|
||||
@ -274,13 +293,77 @@ object AbacRuleEngine {
|
||||
// Compile and execute the rule
|
||||
compiledFunc <- compileRule(ruleId, rule.ruleCode)
|
||||
result <- tryo {
|
||||
compiledFunc(authenticatedUser, authenticatedUserAttributes, authenticatedUserAuthContext, onBehalfOfUserOpt, onBehalfOfUserAttributes, onBehalfOfUserAuthContext, userOpt, userAttributes, bankOpt, bankAttributes, accountOpt, accountAttributes, transactionOpt, transactionAttributes, transactionRequestOpt, transactionRequestAttributes, customerOpt, customerAttributes, Some(callContext))
|
||||
compiledFunc(authenticatedUser, authenticatedUserAttributes, authenticatedUserAuthContext, authenticatedUserEntitlements, onBehalfOfUserOpt, onBehalfOfUserAttributes, onBehalfOfUserAuthContext, onBehalfOfUserEntitlements, userOpt, userAttributes, bankOpt, bankAttributes, accountOpt, accountAttributes, transactionOpt, transactionAttributes, transactionRequestOpt, transactionRequestAttributes, customerOpt, customerAttributes, Some(callContext))
|
||||
}
|
||||
} yield result
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Execute all active ABAC rules with a specific policy (OR logic - at least one must pass)
|
||||
* @param logic The logic to apply: "AND" (all must pass), "OR" (any must pass), "XOR" (exactly one must pass)
|
||||
*
|
||||
* @param policy The policy to filter rules by
|
||||
* @param authenticatedUserId The ID of the authenticated user
|
||||
* @param onBehalfOfUserId Optional ID of user being acted on behalf of
|
||||
* @param userId The ID of the target user to evaluate
|
||||
* @param callContext Call context for fetching objects
|
||||
* @param bankId Optional bank ID
|
||||
* @param accountId Optional account ID
|
||||
* @param viewId Optional view ID
|
||||
* @param transactionId Optional transaction ID
|
||||
* @param transactionRequestId Optional transaction request ID
|
||||
* @param customerId Optional customer ID
|
||||
* @return Box[Boolean] - Full(true) if at least one rule passes (OR logic), Full(false) if all fail
|
||||
*/
|
||||
def executeRulesByPolicy(
|
||||
policy: String,
|
||||
authenticatedUserId: String,
|
||||
onBehalfOfUserId: Option[String] = None,
|
||||
userId: Option[String] = None,
|
||||
callContext: CallContext,
|
||||
bankId: Option[String] = None,
|
||||
accountId: Option[String] = None,
|
||||
viewId: Option[String] = None,
|
||||
transactionId: Option[String] = None,
|
||||
transactionRequestId: Option[String] = None,
|
||||
customerId: Option[String] = None
|
||||
): Box[Boolean] = {
|
||||
val rules = MappedAbacRuleProvider.getActiveAbacRulesByPolicy(policy)
|
||||
|
||||
if (rules.isEmpty) {
|
||||
// No rules for this policy - default to allow
|
||||
Full(true)
|
||||
} else {
|
||||
// Execute all rules and check if at least one passes
|
||||
val results = rules.map { rule =>
|
||||
executeRule(
|
||||
ruleId = rule.abacRuleId,
|
||||
authenticatedUserId = authenticatedUserId,
|
||||
onBehalfOfUserId = onBehalfOfUserId,
|
||||
userId = userId,
|
||||
callContext = callContext,
|
||||
bankId = bankId,
|
||||
accountId = accountId,
|
||||
viewId = viewId,
|
||||
transactionId = transactionId,
|
||||
transactionRequestId = transactionRequestId,
|
||||
customerId = customerId
|
||||
)
|
||||
}
|
||||
|
||||
// Count successes and failures
|
||||
val successes = results.filter {
|
||||
case Full(true) => true
|
||||
case _ => false
|
||||
}
|
||||
|
||||
// At least one rule must pass (OR logic)
|
||||
Full(successes.nonEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate ABAC rule code by attempting to compile it
|
||||
*
|
||||
|
||||
@ -14,6 +14,7 @@ trait AbacRuleTrait {
|
||||
def ruleCode: String
|
||||
def isActive: Boolean
|
||||
def description: String
|
||||
def policy: String
|
||||
def createdByUserId: String
|
||||
def updatedByUserId: String
|
||||
}
|
||||
@ -30,6 +31,7 @@ class AbacRule extends AbacRuleTrait with LongKeyedMapper[AbacRule] with IdPK wi
|
||||
override def defaultValue = true
|
||||
}
|
||||
object Description extends MappedText(this)
|
||||
object Policy extends MappedText(this)
|
||||
object CreatedByUserId extends MappedString(this, 255)
|
||||
object UpdatedByUserId extends MappedString(this, 255)
|
||||
|
||||
@ -38,6 +40,7 @@ class AbacRule extends AbacRuleTrait with LongKeyedMapper[AbacRule] with IdPK wi
|
||||
override def ruleCode: String = RuleCode.get
|
||||
override def isActive: Boolean = IsActive.get
|
||||
override def description: String = Description.get
|
||||
override def policy: String = Policy.get
|
||||
override def createdByUserId: String = CreatedByUserId.get
|
||||
override def updatedByUserId: String = UpdatedByUserId.get
|
||||
}
|
||||
@ -51,10 +54,13 @@ trait AbacRuleProvider {
|
||||
def getAbacRuleByName(ruleName: String): Box[AbacRuleTrait]
|
||||
def getAllAbacRules(): List[AbacRuleTrait]
|
||||
def getActiveAbacRules(): List[AbacRuleTrait]
|
||||
def getAbacRulesByPolicy(policy: String): List[AbacRuleTrait]
|
||||
def getActiveAbacRulesByPolicy(policy: String): List[AbacRuleTrait]
|
||||
def createAbacRule(
|
||||
ruleName: String,
|
||||
ruleCode: String,
|
||||
description: String,
|
||||
policy: String,
|
||||
isActive: Boolean,
|
||||
createdBy: String
|
||||
): Box[AbacRuleTrait]
|
||||
@ -63,6 +69,7 @@ trait AbacRuleProvider {
|
||||
ruleName: String,
|
||||
ruleCode: String,
|
||||
description: String,
|
||||
policy: String,
|
||||
isActive: Boolean,
|
||||
updatedBy: String
|
||||
): Box[AbacRuleTrait]
|
||||
@ -87,10 +94,23 @@ object MappedAbacRuleProvider extends AbacRuleProvider {
|
||||
AbacRule.findAll(By(AbacRule.IsActive, true))
|
||||
}
|
||||
|
||||
override def getAbacRulesByPolicy(policy: String): List[AbacRuleTrait] = {
|
||||
AbacRule.findAll().filter { rule =>
|
||||
rule.policy.split(",").map(_.trim).contains(policy)
|
||||
}
|
||||
}
|
||||
|
||||
override def getActiveAbacRulesByPolicy(policy: String): List[AbacRuleTrait] = {
|
||||
AbacRule.findAll(By(AbacRule.IsActive, true)).filter { rule =>
|
||||
rule.policy.split(",").map(_.trim).contains(policy)
|
||||
}
|
||||
}
|
||||
|
||||
override def createAbacRule(
|
||||
ruleName: String,
|
||||
ruleCode: String,
|
||||
description: String,
|
||||
policy: String,
|
||||
isActive: Boolean,
|
||||
createdBy: String
|
||||
): Box[AbacRuleTrait] = {
|
||||
@ -99,6 +119,7 @@ object MappedAbacRuleProvider extends AbacRuleProvider {
|
||||
.RuleName(ruleName)
|
||||
.RuleCode(ruleCode)
|
||||
.Description(description)
|
||||
.Policy(policy)
|
||||
.IsActive(isActive)
|
||||
.CreatedByUserId(createdBy)
|
||||
.UpdatedByUserId(createdBy)
|
||||
@ -111,6 +132,7 @@ object MappedAbacRuleProvider extends AbacRuleProvider {
|
||||
ruleName: String,
|
||||
ruleCode: String,
|
||||
description: String,
|
||||
policy: String,
|
||||
isActive: Boolean,
|
||||
updatedBy: String
|
||||
): Box[AbacRuleTrait] = {
|
||||
@ -121,6 +143,7 @@ object MappedAbacRuleProvider extends AbacRuleProvider {
|
||||
.RuleName(ruleName)
|
||||
.RuleCode(ruleCode)
|
||||
.Description(description)
|
||||
.Policy(policy)
|
||||
.IsActive(isActive)
|
||||
.UpdatedByUserId(updatedBy)
|
||||
.saveMe()
|
||||
|
||||
@ -1257,3 +1257,4 @@ so the caller must specify any required filtering by catalog explicitly.
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -266,6 +266,19 @@ object Constant extends MdcLoggable {
|
||||
// ABAC Cache Prefixes (with global namespace and versioning)
|
||||
def ABAC_RULE_PREFIX: String = getVersionedCachePrefix(ABAC_RULE_NAMESPACE)
|
||||
|
||||
// ABAC Policy Constants
|
||||
final val ABAC_POLICY_ACCOUNT_ACCESS = "account-access"
|
||||
|
||||
// List of all ABAC Policies
|
||||
final val ABAC_POLICIES: List[String] = List(
|
||||
ABAC_POLICY_ACCOUNT_ACCESS
|
||||
)
|
||||
|
||||
// Map of ABAC Policies to their descriptions
|
||||
final val ABAC_POLICY_DESCRIPTIONS: Map[String, String] = Map(
|
||||
ABAC_POLICY_ACCOUNT_ACCESS -> "Rules for controlling access to account information and account-related operations"
|
||||
)
|
||||
|
||||
final val CAN_SEE_TRANSACTION_OTHER_BANK_ACCOUNT = "can_see_transaction_other_bank_account"
|
||||
final val CAN_SEE_TRANSACTION_METADATA = "can_see_transaction_metadata"
|
||||
final val CAN_SEE_TRANSACTION_DESCRIPTION = "can_see_transaction_description"
|
||||
|
||||
@ -1638,6 +1638,11 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{
|
||||
var errorResponseBodies: List[String], // Possible error responses
|
||||
tags: List[ResourceDocTag],
|
||||
var roles: Option[List[ApiRole]] = None,
|
||||
// IMPORTANT: Roles declared here are AUTOMATICALLY CHECKED at runtime!
|
||||
// When roles specified, framework automatically: 1) Validates user authentication,
|
||||
// 2) Checks user has at least one of specified roles, 3) Performs checks in wrappedWithAuthCheck()
|
||||
// No manual hasEntitlement() call needed in endpoint body - handled automatically!
|
||||
// To disable: call .disableAutoValidateRoles() on ResourceDoc
|
||||
isFeatured: Boolean = false,
|
||||
specialInstructions: Option[String] = None,
|
||||
var specifiedUrl: Option[String] = None, // A derived value: Contains the called version (added at run time). See the resource doc for resource doc!
|
||||
|
||||
@ -124,7 +124,7 @@ object ApiRole extends MdcLoggable{
|
||||
// ALL
|
||||
case class CanGetSystemLogCacheAll(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetSystemLogCacheAll = CanGetSystemLogCacheAll()
|
||||
|
||||
|
||||
case class CanUpdateAgentStatusAtAnyBank(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canUpdateAgentStatusAtAnyBank = CanUpdateAgentStatusAtAnyBank()
|
||||
|
||||
@ -1003,6 +1003,9 @@ object ApiRole extends MdcLoggable{
|
||||
case class CanGetAllConnectorMethods(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetAllConnectorMethods = CanGetAllConnectorMethods()
|
||||
|
||||
case class CanGetSystemConnectorMethodNames(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canGetSystemConnectorMethodNames = CanGetSystemConnectorMethodNames()
|
||||
|
||||
case class CanCreateDynamicResourceDoc(requiresBankId: Boolean = false) extends ApiRole
|
||||
lazy val canCreateDynamicResourceDoc = CanCreateDynamicResourceDoc()
|
||||
|
||||
|
||||
@ -18,6 +18,8 @@ object ApiTag {
|
||||
val apiTagTransactionRequestAttribute = ResourceDocTag("Transaction-Request-Attribute")
|
||||
val apiTagVrp = ResourceDocTag("VRP")
|
||||
val apiTagApi = ResourceDocTag("API")
|
||||
val apiTagOAuth = ResourceDocTag("OAuth")
|
||||
val apiTagOIDC = ResourceDocTag("OIDC")
|
||||
val apiTagBank = ResourceDocTag("Bank")
|
||||
val apiTagBankAttribute = ResourceDocTag("Bank-Attribute")
|
||||
val apiTagAccount = ResourceDocTag("Account")
|
||||
|
||||
@ -1864,7 +1864,7 @@ trait APIMethods310 {
|
||||
List(
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagApi))
|
||||
List(apiTagApi, apiTagOAuth, apiTagOIDC))
|
||||
|
||||
lazy val getObpConnectorLoopback : OBPEndpoint = {
|
||||
case "connector" :: "loopback" :: Nil JsonGet _ => {
|
||||
@ -4112,7 +4112,7 @@ trait APIMethods310 {
|
||||
List(
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagApi))
|
||||
List(apiTagApi, apiTagOAuth, apiTagOIDC))
|
||||
|
||||
lazy val getOAuth2ServerJWKsURIs: OBPEndpoint = {
|
||||
case "jwks-uris" :: Nil JsonGet _ => {
|
||||
|
||||
@ -1075,7 +1075,24 @@ trait APIMethods600 {
|
||||
entitlements <- NewStyle.function.getEntitlementsByUserId(u.userId, callContext)
|
||||
} yield {
|
||||
val permissions: Option[Permission] = Views.views.vend.getPermissionForUser(u).toOption
|
||||
val currentUser = UserV600(u, entitlements, permissions)
|
||||
// Add SuperAdmin virtual entitlement if user is super admin
|
||||
val finalEntitlements = if (APIUtil.isSuperAdmin(u.userId)) {
|
||||
// Create a virtual SuperAdmin entitlement
|
||||
val superAdminEntitlement: Entitlement = new Entitlement {
|
||||
def entitlementId: String = ""
|
||||
def bankId: String = ""
|
||||
def userId: String = u.userId
|
||||
def roleName: String = "SuperAdmin"
|
||||
def createdByProcess: String = "System"
|
||||
def entitlementRequestId: Option[String] = None
|
||||
def groupId: Option[String] = None
|
||||
def process: Option[String] = None
|
||||
}
|
||||
entitlements ::: List(superAdminEntitlement)
|
||||
} else {
|
||||
entitlements
|
||||
}
|
||||
val currentUser = UserV600(u, finalEntitlements, permissions)
|
||||
val onBehalfOfUser = if(cc.onBehalfOfUser.isDefined) {
|
||||
val user = cc.onBehalfOfUser.toOption.get
|
||||
val entitlements = Entitlement.entitlement.vend.getEntitlementsByUserId(user.userId).headOption.toList.flatten
|
||||
@ -1649,7 +1666,7 @@ trait APIMethods600 {
|
||||
|
|
||||
|${userAuthenticationMessage(true)}
|
||||
|
|
||||
|CanGetMethodRoutings entitlement is required.
|
||||
|CanGetSystemConnectorMethodNames entitlement is required.
|
||||
|
|
||||
""".stripMargin,
|
||||
EmptyBody,
|
||||
@ -1659,8 +1676,8 @@ trait APIMethods600 {
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagSystem, apiTagMethodRouting, apiTagApi),
|
||||
Some(List(canGetMethodRoutings))
|
||||
List(apiTagConnectorMethod, apiTagSystem, apiTagMethodRouting, apiTagApi),
|
||||
Some(List(canGetSystemConnectorMethodNames))
|
||||
)
|
||||
|
||||
lazy val getConnectorMethodNames: OBPEndpoint = {
|
||||
@ -1668,7 +1685,6 @@ trait APIMethods600 {
|
||||
cc => implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
(Full(u), callContext) <- authenticatedAccess(cc)
|
||||
_ <- NewStyle.function.hasEntitlement("", u.userId, canGetMethodRoutings, callContext)
|
||||
// Fetch connector method names with caching
|
||||
methodNames <- Future {
|
||||
/**
|
||||
@ -4734,6 +4750,7 @@ trait APIMethods600 {
|
||||
rule_name = "admin_only",
|
||||
rule_code = """user.emailAddress.contains("admin")""",
|
||||
description = "Only allow access to users with admin email",
|
||||
policy = "user-access,admin",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleJsonV600(
|
||||
@ -4742,6 +4759,7 @@ trait APIMethods600 {
|
||||
rule_code = """user.emailAddress.contains("admin")""",
|
||||
is_active = true,
|
||||
description = "Only allow access to users with admin email",
|
||||
policy = "user-access,admin",
|
||||
created_by_user_id = "user123",
|
||||
updated_by_user_id = "user123"
|
||||
),
|
||||
@ -4781,6 +4799,7 @@ trait APIMethods600 {
|
||||
ruleName = createJson.rule_name,
|
||||
ruleCode = createJson.rule_code,
|
||||
description = createJson.description,
|
||||
policy = createJson.policy,
|
||||
isActive = createJson.is_active,
|
||||
createdBy = user.userId
|
||||
)
|
||||
@ -4817,6 +4836,7 @@ trait APIMethods600 {
|
||||
rule_code = """user.emailAddress.contains("admin")""",
|
||||
is_active = true,
|
||||
description = "Only allow access to users with admin email",
|
||||
policy = "user-access,admin",
|
||||
created_by_user_id = "user123",
|
||||
updated_by_user_id = "user123"
|
||||
),
|
||||
@ -4872,6 +4892,7 @@ trait APIMethods600 {
|
||||
rule_code = """user.emailAddress.contains("admin")""",
|
||||
is_active = true,
|
||||
description = "Only allow access to users with admin email",
|
||||
policy = "user-access,admin",
|
||||
created_by_user_id = "user123",
|
||||
updated_by_user_id = "user123"
|
||||
)
|
||||
@ -4901,6 +4922,75 @@ trait APIMethods600 {
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getAbacRulesByPolicy,
|
||||
implementedInApiVersion,
|
||||
nameOf(getAbacRulesByPolicy),
|
||||
"GET",
|
||||
"/management/abac-rules/policy/POLICY",
|
||||
"Get ABAC Rules by Policy",
|
||||
s"""Get all ABAC rules that belong to a specific policy.
|
||||
|
|
||||
|Multiple rules can share the same policy. Rules with multiple policies (comma-separated)
|
||||
|will be returned if any of their policies match the requested policy.
|
||||
|
|
||||
|**Documentation:**
|
||||
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|
||||
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|
||||
|- ${Glossary.getGlossaryItemLink("ABAC_Object_Properties_Reference")} - Detailed property reference
|
||||
|
|
||||
|${userAuthenticationMessage(true)}
|
||||
|
|
||||
|""".stripMargin,
|
||||
EmptyBody,
|
||||
AbacRulesJsonV600(
|
||||
abac_rules = List(
|
||||
AbacRuleJsonV600(
|
||||
abac_rule_id = "abc123",
|
||||
rule_name = "admin_only",
|
||||
rule_code = """user.emailAddress.contains("admin")""",
|
||||
is_active = true,
|
||||
description = "Only allow access to users with admin email",
|
||||
policy = "user-access,admin",
|
||||
created_by_user_id = "user123",
|
||||
updated_by_user_id = "user123"
|
||||
),
|
||||
AbacRuleJsonV600(
|
||||
abac_rule_id = "def456",
|
||||
rule_name = "admin_department_check",
|
||||
rule_code = """user.department == "admin"""",
|
||||
is_active = true,
|
||||
description = "Check if user is in admin department",
|
||||
policy = "admin",
|
||||
created_by_user_id = "user123",
|
||||
updated_by_user_id = "user123"
|
||||
)
|
||||
)
|
||||
),
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagABAC),
|
||||
Some(List(canGetAbacRule))
|
||||
)
|
||||
|
||||
lazy val getAbacRulesByPolicy: OBPEndpoint = {
|
||||
case "management" :: "abac-rules" :: "policy" :: policy :: Nil JsonGet _ => {
|
||||
cc => implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
(Full(user), callContext) <- authenticatedAccess(cc)
|
||||
_ <- NewStyle.function.hasEntitlement("", user.userId, canGetAbacRule, callContext)
|
||||
rules <- Future {
|
||||
MappedAbacRuleProvider.getAbacRulesByPolicy(policy)
|
||||
}
|
||||
} yield {
|
||||
(createAbacRulesJsonV600(rules), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
updateAbacRule,
|
||||
implementedInApiVersion,
|
||||
@ -4922,6 +5012,7 @@ trait APIMethods600 {
|
||||
rule_name = "admin_only_updated",
|
||||
rule_code = """user.emailAddress.contains("admin") && user.provider == "obp"""",
|
||||
description = "Only allow access to OBP admin users",
|
||||
policy = "user-access,admin,obp",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleJsonV600(
|
||||
@ -4930,6 +5021,7 @@ trait APIMethods600 {
|
||||
rule_code = """user.emailAddress.contains("admin") && user.provider == "obp"""",
|
||||
is_active = true,
|
||||
description = "Only allow access to OBP admin users",
|
||||
policy = "user-access,admin,obp",
|
||||
created_by_user_id = "user123",
|
||||
updated_by_user_id = "user456"
|
||||
),
|
||||
@ -4964,6 +5056,7 @@ trait APIMethods600 {
|
||||
ruleName = updateJson.rule_name,
|
||||
ruleCode = updateJson.rule_code,
|
||||
description = updateJson.description,
|
||||
policy = updateJson.policy,
|
||||
isActive = updateJson.is_active,
|
||||
updatedBy = user.userId
|
||||
)
|
||||
@ -5078,16 +5171,18 @@ trait APIMethods600 {
|
||||
),
|
||||
examples = List(
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Access",
|
||||
title = "Check User Identity",
|
||||
code = "authenticatedUser.userId == user.userId",
|
||||
description = "Verify that the authenticated user matches the target user"
|
||||
rule_name = "Check User Identity",
|
||||
rule_code = "authenticatedUser.userId == user.userId",
|
||||
description = "Verify that the authenticated user matches the target user",
|
||||
policy = "user-access",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Bank Access",
|
||||
title = "Check Specific Bank",
|
||||
code = "bankOpt.isDefined && bankOpt.get.bankId.value == \"gh.29.uk\"",
|
||||
description = "Verify that the bank context is defined and matches a specific bank ID"
|
||||
rule_name = "Check Specific Bank",
|
||||
rule_code = "bankOpt.isDefined && bankOpt.get.bankId.value == \"gh.29.uk\"",
|
||||
policy = "bank-access",
|
||||
description = "Verify that the bank context is defined and matches a specific bank ID",
|
||||
is_active = true
|
||||
)
|
||||
),
|
||||
available_operators = List("==", "!=", "&&", "||", "!", ">", "<", ">=", "<=", "contains", "isDefined"),
|
||||
@ -5118,9 +5213,11 @@ trait APIMethods600 {
|
||||
AbacParameterJsonV600("authenticatedUser", "User", "The logged-in user (always present)", required = true, "User"),
|
||||
AbacParameterJsonV600("authenticatedUserAttributes", "List[UserAttributeTrait]", "Non-personal attributes of authenticated user", required = true, "User"),
|
||||
AbacParameterJsonV600("authenticatedUserAuthContext", "List[UserAuthContext]", "Auth context of authenticated user", required = true, "User"),
|
||||
AbacParameterJsonV600("authenticatedUserEntitlements", "List[Entitlement]", "Entitlements (roles) of authenticated user", required = true, "User"),
|
||||
AbacParameterJsonV600("onBehalfOfUserOpt", "Option[User]", "User being acted on behalf of (delegation)", required = false, "User"),
|
||||
AbacParameterJsonV600("onBehalfOfUserAttributes", "List[UserAttributeTrait]", "Attributes of delegation user", required = false, "User"),
|
||||
AbacParameterJsonV600("onBehalfOfUserAuthContext", "List[UserAuthContext]", "Auth context of delegation user", required = false, "User"),
|
||||
AbacParameterJsonV600("onBehalfOfUserEntitlements", "List[Entitlement]", "Entitlements (roles) of delegation user", required = false, "User"),
|
||||
AbacParameterJsonV600("userOpt", "Option[User]", "Target user being evaluated", required = false, "User"),
|
||||
AbacParameterJsonV600("userAttributes", "List[UserAttributeTrait]", "Attributes of target user", required = false, "User"),
|
||||
AbacParameterJsonV600("bankOpt", "Option[Bank]", "Bank context", required = false, "Bank"),
|
||||
@ -5225,6 +5322,12 @@ trait APIMethods600 {
|
||||
AbacObjectPropertyJsonV600("value", "String", "Attribute value"),
|
||||
AbacObjectPropertyJsonV600("attributeType", "AttributeType", "Attribute type")
|
||||
)),
|
||||
AbacObjectTypeJsonV600("Entitlement", "User entitlement (role)", List(
|
||||
AbacObjectPropertyJsonV600("entitlementId", "String", "Entitlement ID"),
|
||||
AbacObjectPropertyJsonV600("roleName", "String", "Role name (e.g., CanCreateAccount, CanReadTransactions)"),
|
||||
AbacObjectPropertyJsonV600("bankId", "String", "Bank ID (empty string for system-wide roles)"),
|
||||
AbacObjectPropertyJsonV600("userId", "String", "User ID this entitlement belongs to")
|
||||
)),
|
||||
AbacObjectTypeJsonV600("CallContext", "Request context with metadata", List(
|
||||
AbacObjectPropertyJsonV600("correlationId", "String", "Correlation ID for request tracking"),
|
||||
AbacObjectPropertyJsonV600("url", "Option[String]", "Request URL"),
|
||||
@ -5238,538 +5341,60 @@ trait APIMethods600 {
|
||||
),
|
||||
examples = List(
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Authenticated User",
|
||||
title = "Check Email Domain",
|
||||
code = "authenticatedUser.emailAddress.contains(\"@example.com\")",
|
||||
description = "Verify that the authenticated user's email belongs to a specific domain"
|
||||
rule_name = "Branch Manager Internal Account Access",
|
||||
rule_code = "authenticatedUserEntitlements.exists(e => e.roleName == \"CanReadAccountsAtOneBank\") && authenticatedUserAttributes.exists(a => a.name == \"branch\" && accountAttributes.exists(aa => aa.name == \"branch\" && a.value == aa.value)) && callContext.exists(_.verb.exists(_ == \"GET\")) && accountOpt.exists(_.accountType == \"CURRENT\")",
|
||||
description = "Allow GET access to current accounts when user has CanReadAccountsAtOneBank role and branch matches account's branch",
|
||||
policy = "account-access",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Authenticated User",
|
||||
title = "Check Authentication Provider",
|
||||
code = "authenticatedUser.provider == \"obp\"",
|
||||
description = "Verify the authentication provider is OBP"
|
||||
rule_name = "Internal Network High-Value Transaction Review",
|
||||
rule_code = "callContext.exists(_.ipAddress.exists(_.startsWith(\"10.\"))) && authenticatedUserEntitlements.exists(e => e.roleName == \"CanReadTransactionsAtOneBank\") && transactionOpt.exists(_.amount > 10000)",
|
||||
description = "Allow users with CanReadTransactionsAtOneBank role on internal network to review high-value transactions over 10,000",
|
||||
policy = "transaction-access",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Authenticated User",
|
||||
title = "Compare Authenticated to Target User",
|
||||
code = "authenticatedUser.userId == userOpt.get.userId",
|
||||
description = "Check if authenticated user matches the target user (unsafe - use exists instead)"
|
||||
rule_name = "Department Head Same-Department Account Read where overdrawn",
|
||||
rule_code = "authenticatedUserEntitlements.exists(e => e.roleName == \"CanReadAccountsAtOneBank\") && authenticatedUserAttributes.exists(ua => ua.name == \"department\" && accountAttributes.exists(aa => aa.name == \"department\" && ua.value == aa.value)) && callContext.exists(_.url.exists(_.contains(\"/accounts/\"))) && accountOpt.exists(_.balance < 0)",
|
||||
description = "Allow users with CanReadAccountsAtOneBank role to read overdrawn accounts in their department",
|
||||
policy = "account-access",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Authenticated User",
|
||||
title = "Check User Not Deleted",
|
||||
code = "!authenticatedUser.isDeleted.getOrElse(false)",
|
||||
description = "Verify the authenticated user is not marked as deleted"
|
||||
rule_name = "Manager Internal Network Transaction Approval",
|
||||
rule_code = "authenticatedUserEntitlements.exists(e => e.roleName == \"CanCreateTransactionRequest\") && callContext.exists(_.ipAddress.exists(ip => ip.startsWith(\"10.\") || ip.startsWith(\"192.168.\"))) && transactionRequestOpt.exists(tr => tr.status == \"PENDING\" && tr.charge.value.toDouble < 50000)",
|
||||
description = "Allow users with CanCreateTransactionRequest role on internal network to approve pending transaction requests under 50,000",
|
||||
policy = "transaction-request",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Attributes - Authenticated User",
|
||||
title = "Check Admin Role",
|
||||
code = "authenticatedUserAttributes.exists(attr => attr.name == \"role\" && attr.value == \"admin\")",
|
||||
description = "Check if authenticated user has admin role attribute"
|
||||
rule_name = "KYC Officer Customer Creation from Branch",
|
||||
rule_code = "authenticatedUserEntitlements.exists(e => e.roleName == \"CanCreateCustomer\") && authenticatedUserAttributes.exists(a => a.name == \"certification\" && a.value == \"kyc_certified\") && callContext.exists(_.verb.exists(_ == \"POST\")) && callContext.exists(_.ipAddress.exists(_.startsWith(\"10.20.\"))) && customerAttributes.exists(ca => ca.name == \"onboarding_status\" && ca.value == \"pending\")",
|
||||
description = "Allow users with CanCreateCustomer role and KYC certification to create customers via POST from branch network (10.20.x.x) when status is pending",
|
||||
policy = "customer-access",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Attributes - Authenticated User",
|
||||
title = "Check Department",
|
||||
code = "authenticatedUserAttributes.find(_.name == \"department\").exists(_.value == \"finance\")",
|
||||
description = "Check if authenticated user belongs to finance department"
|
||||
rule_name = "International Team Foreign Currency Transaction",
|
||||
rule_code = "authenticatedUserEntitlements.exists(e => e.roleName == \"CanReadTransactionsAtOneBank\") && authenticatedUserAttributes.exists(a => a.name == \"team\" && a.value == \"international\") && callContext.exists(_.url.exists(_.contains(\"/transactions/\"))) && transactionOpt.exists(t => t.currency != \"USD\" && t.amount < 100000) && accountOpt.exists(a => accountAttributes.exists(aa => aa.name == \"international_enabled\" && aa.value == \"true\"))",
|
||||
description = "Allow international team users with CanReadTransactionsAtOneBank role to access foreign currency transactions under 100k on international-enabled accounts",
|
||||
policy = "transaction-access",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Attributes - Authenticated User",
|
||||
title = "Check Multiple Roles",
|
||||
code = "authenticatedUserAttributes.exists(attr => attr.name == \"role\" && List(\"admin\", \"manager\").contains(attr.value))",
|
||||
description = "Check if authenticated user has admin or manager role"
|
||||
rule_name = "Assistant with Limited Delegation Account View",
|
||||
rule_code = "onBehalfOfUserOpt.isDefined && onBehalfOfUserEntitlements.exists(e => e.roleName == \"CanReadAccountsAtOneBank\") && authenticatedUserAttributes.exists(a => a.name == \"assistant_of\" && onBehalfOfUserOpt.exists(u => a.value == u.userId)) && callContext.exists(_.verb.exists(_ == \"GET\")) && accountOpt.exists(a => accountAttributes.exists(aa => aa.name == \"tier\" && List(\"gold\", \"platinum\").contains(aa.value)))",
|
||||
description = "Allow assistants to view gold/platinum accounts via GET when acting on behalf of a user with CanReadAccountsAtOneBank role",
|
||||
policy = "account-access",
|
||||
is_active = true
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Auth Context",
|
||||
title = "Check Session Type",
|
||||
code = "authenticatedUserAuthContext.exists(_.key == \"session_type\" && _.value == \"secure\")",
|
||||
description = "Verify the session type is secure"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Auth Context",
|
||||
title = "Check Auth Method",
|
||||
code = "authenticatedUserAuthContext.exists(_.key == \"auth_method\" && _.value == \"certificate\")",
|
||||
description = "Verify authentication was done via certificate"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Delegation",
|
||||
title = "Check Delegated User Email Domain",
|
||||
code = "onBehalfOfUserOpt.exists(_.emailAddress.endsWith(\"@company.com\"))",
|
||||
description = "Check if delegation user belongs to specific company domain"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Delegation",
|
||||
title = "Check No Delegation or Self Delegation",
|
||||
code = "onBehalfOfUserOpt.isEmpty || onBehalfOfUserOpt.get.userId == authenticatedUser.userId",
|
||||
description = "Allow if no delegation or user delegating to themselves"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Delegation",
|
||||
title = "Check Different User Delegation",
|
||||
code = "onBehalfOfUserOpt.forall(_.userId != authenticatedUser.userId)",
|
||||
description = "Check that delegation is to a different user (if present)"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Attributes - Delegation",
|
||||
title = "Check Delegation Level",
|
||||
code = "onBehalfOfUserAttributes.exists(attr => attr.name == \"delegation_level\" && attr.value == \"full\")",
|
||||
description = "Check if delegation has full permission level"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Attributes - Delegation",
|
||||
title = "Check Authorized Delegation",
|
||||
code = "onBehalfOfUserAttributes.isEmpty || onBehalfOfUserAttributes.exists(_.name == \"authorized\")",
|
||||
description = "Allow if no delegation attributes or has authorized attribute"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Target User",
|
||||
title = "Check Self Access",
|
||||
code = "userOpt.isDefined && userOpt.get.userId == authenticatedUser.userId",
|
||||
description = "Check if target user is the authenticated user (self-access)"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Target User",
|
||||
title = "Check Target User Provider",
|
||||
code = "userOpt.exists(_.provider == \"obp\")",
|
||||
description = "Check if target user is authenticated via OBP provider"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Target User",
|
||||
title = "Check Target User Email Domain",
|
||||
code = "userOpt.exists(_.emailAddress.endsWith(\"@trusted.com\"))",
|
||||
description = "Check if target user belongs to trusted domain"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User - Target User",
|
||||
title = "Check Target User Active",
|
||||
code = "userOpt.forall(!_.isDeleted.getOrElse(false))",
|
||||
description = "Ensure target user is not deleted (if present)"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Attributes - Target User",
|
||||
title = "Check Premium Account",
|
||||
code = "userAttributes.exists(attr => attr.name == \"account_type\" && attr.value == \"premium\")",
|
||||
description = "Check if target user has premium account type"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Attributes - Target User",
|
||||
title = "Check KYC Status",
|
||||
code = "userAttributes.exists(attr => attr.name == \"kyc_status\" && attr.value == \"verified\")",
|
||||
description = "Check if target user has verified KYC status"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "User Attributes - Target User",
|
||||
title = "Check User Tier Level",
|
||||
code = "userAttributes.find(_.name == \"tier\").exists(_.value.toInt >= 2)",
|
||||
description = "Check if user tier is 2 or higher"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Bank",
|
||||
title = "Check Specific Bank ID",
|
||||
code = "bankOpt.isDefined && bankOpt.get.bankId.value == \"gh.29.uk\"",
|
||||
description = "Check if bank context is defined and matches specific bank ID"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Bank",
|
||||
title = "Check Bank Name Contains Text",
|
||||
code = "bankOpt.exists(_.fullName.contains(\"Community\"))",
|
||||
description = "Check if bank full name contains specific text"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Bank",
|
||||
title = "Check Bank Has HTTPS Website",
|
||||
code = "bankOpt.exists(_.websiteUrl.contains(\"https://\"))",
|
||||
description = "Check if bank website uses HTTPS"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Bank Attributes",
|
||||
title = "Check Bank Region",
|
||||
code = "bankAttributes.exists(attr => attr.name == \"region\" && attr.value == \"EU\")",
|
||||
description = "Check if bank is in EU region"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Bank Attributes",
|
||||
title = "Check Bank Certification",
|
||||
code = "bankAttributes.exists(attr => attr.name == \"certified\" && attr.value == \"true\")",
|
||||
description = "Check if bank has certification attribute"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Account",
|
||||
title = "Check Minimum Balance",
|
||||
code = "accountOpt.isDefined && accountOpt.get.balance > 1000",
|
||||
description = "Check if account balance is above threshold"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Account",
|
||||
title = "Check USD Account Balance",
|
||||
code = "accountOpt.exists(acc => acc.currency == \"USD\" && acc.balance > 5000)",
|
||||
description = "Check if USD account has balance above $5000"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Account",
|
||||
title = "Check Account Type",
|
||||
code = "accountOpt.exists(_.accountType == \"SAVINGS\")",
|
||||
description = "Check if account is a savings account"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Account",
|
||||
title = "Check Account Number Length",
|
||||
code = "accountOpt.exists(_.number.length >= 10)",
|
||||
description = "Check if account number has minimum length"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Account Attributes",
|
||||
title = "Check Account Status",
|
||||
code = "accountAttributes.exists(attr => attr.name == \"status\" && attr.value == \"active\")",
|
||||
description = "Check if account has active status"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Account Attributes",
|
||||
title = "Check Account Tier",
|
||||
code = "accountAttributes.exists(attr => attr.name == \"account_tier\" && attr.value == \"gold\")",
|
||||
description = "Check if account has gold tier"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction",
|
||||
title = "Check Transaction Amount Limit",
|
||||
code = "transactionOpt.isDefined && transactionOpt.get.amount < 10000",
|
||||
description = "Check if transaction amount is below limit"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction",
|
||||
title = "Check Transaction Type",
|
||||
code = "transactionOpt.exists(_.transactionType.contains(\"TRANSFER\"))",
|
||||
description = "Check if transaction is a transfer type"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction",
|
||||
title = "Check EUR Transaction Amount",
|
||||
code = "transactionOpt.exists(t => t.currency == \"EUR\" && t.amount > 100)",
|
||||
description = "Check if EUR transaction exceeds €100"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction",
|
||||
title = "Check Positive Balance After Transaction",
|
||||
code = "transactionOpt.exists(_.balance > 0)",
|
||||
description = "Check if balance remains positive after transaction"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction Attributes",
|
||||
title = "Check Transaction Category",
|
||||
code = "transactionAttributes.exists(attr => attr.name == \"category\" && attr.value == \"business\")",
|
||||
description = "Check if transaction is categorized as business"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction Attributes",
|
||||
title = "Check Transaction Not Flagged",
|
||||
code = "!transactionAttributes.exists(attr => attr.name == \"flagged\" && attr.value == \"true\")",
|
||||
description = "Check that transaction is not flagged"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction Request",
|
||||
title = "Check Pending Status",
|
||||
code = "transactionRequestOpt.exists(_.status == \"PENDING\")",
|
||||
description = "Check if transaction request is pending"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction Request",
|
||||
title = "Check SEPA Type",
|
||||
code = "transactionRequestOpt.exists(_.type == \"SEPA\")",
|
||||
description = "Check if transaction request is SEPA type"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction Request",
|
||||
title = "Check Same Bank",
|
||||
code = "transactionRequestOpt.exists(_.this_bank_id.value == bankOpt.get.bankId.value)",
|
||||
description = "Check if transaction request is for the same bank (unsafe - use exists)"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction Request Attributes",
|
||||
title = "Check High Priority",
|
||||
code = "transactionRequestAttributes.exists(attr => attr.name == \"priority\" && attr.value == \"high\")",
|
||||
description = "Check if transaction request has high priority"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Transaction Request Attributes",
|
||||
title = "Check Mobile App Source",
|
||||
code = "transactionRequestAttributes.exists(attr => attr.name == \"source\" && attr.value == \"mobile_app\")",
|
||||
description = "Check if transaction request originated from mobile app"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Customer",
|
||||
title = "Check Corporate Customer",
|
||||
code = "customerOpt.exists(_.legalName.contains(\"Corp\"))",
|
||||
description = "Check if customer legal name contains Corp"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Customer",
|
||||
title = "Check Customer Email Matches User",
|
||||
code = "customerOpt.isDefined && customerOpt.get.email == authenticatedUser.emailAddress",
|
||||
description = "Check if customer email matches authenticated user"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Customer",
|
||||
title = "Check Active Customer Relationship",
|
||||
code = "customerOpt.exists(_.relationshipStatus == \"ACTIVE\")",
|
||||
description = "Check if customer relationship is active"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Customer",
|
||||
title = "Check Customer Has Mobile",
|
||||
code = "customerOpt.exists(_.mobileNumber.nonEmpty)",
|
||||
description = "Check if customer has mobile number on file"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Customer Attributes",
|
||||
title = "Check Low Risk Customer",
|
||||
code = "customerAttributes.exists(attr => attr.name == \"risk_level\" && attr.value == \"low\")",
|
||||
description = "Check if customer has low risk level"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Customer Attributes",
|
||||
title = "Check VIP Status",
|
||||
code = "customerAttributes.exists(attr => attr.name == \"vip_status\" && attr.value == \"true\")",
|
||||
description = "Check if customer has VIP status"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Call Context",
|
||||
title = "Check Internal Network",
|
||||
code = "callContext.exists(_.ipAddress.exists(_.startsWith(\"192.168\")))",
|
||||
description = "Check if request comes from internal network"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Call Context",
|
||||
title = "Check GET Request",
|
||||
code = "callContext.exists(_.verb.exists(_ == \"GET\"))",
|
||||
description = "Check if request is GET method"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Call Context",
|
||||
title = "Check URL Contains Pattern",
|
||||
code = "callContext.exists(_.url.exists(_.contains(\"/accounts/\")))",
|
||||
description = "Check if request URL contains accounts path"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - User Comparisons",
|
||||
title = "Self Access Check",
|
||||
code = "userOpt.exists(_.userId == authenticatedUser.userId)",
|
||||
description = "Check if target user is the authenticated user"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - User Comparisons",
|
||||
title = "Same Email Check",
|
||||
code = "userOpt.exists(_.emailAddress == authenticatedUser.emailAddress)",
|
||||
description = "Check if target user has same email as authenticated user"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - User Comparisons",
|
||||
title = "Same Email Domain",
|
||||
code = "userOpt.exists(u => authenticatedUser.emailAddress.split(\"@\")(1) == u.emailAddress.split(\"@\")(1))",
|
||||
description = "Check if both users belong to same email domain"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - User Comparisons",
|
||||
title = "Delegation Match",
|
||||
code = "onBehalfOfUserOpt.isDefined && userOpt.isDefined && onBehalfOfUserOpt.get.userId == userOpt.get.userId",
|
||||
description = "Check if delegation user matches target user"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - User Comparisons",
|
||||
title = "Different User Access",
|
||||
code = "userOpt.exists(_.userId != authenticatedUser.userId)",
|
||||
description = "Check if accessing a different user's data"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Customer Comparisons",
|
||||
title = "Customer Email Matches Auth User",
|
||||
code = "customerOpt.exists(_.email == authenticatedUser.emailAddress)",
|
||||
description = "Check if customer email matches authenticated user"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Customer Comparisons",
|
||||
title = "Customer Email Matches Target User",
|
||||
code = "customerOpt.isDefined && userOpt.isDefined && customerOpt.get.email == userOpt.get.emailAddress",
|
||||
description = "Check if customer email matches target user email"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Customer Comparisons",
|
||||
title = "Customer Name Contains User Name",
|
||||
code = "customerOpt.exists(c => userOpt.exists(u => c.legalName.contains(u.name)))",
|
||||
description = "Check if customer legal name contains user name"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Account/Transaction",
|
||||
title = "Transaction Within Balance",
|
||||
code = "transactionOpt.isDefined && accountOpt.isDefined && transactionOpt.get.amount < accountOpt.get.balance",
|
||||
description = "Check if transaction amount is less than account balance"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Account/Transaction",
|
||||
title = "Transaction Within 50% Balance",
|
||||
code = "transactionOpt.exists(t => accountOpt.exists(a => t.amount <= a.balance * 0.5))",
|
||||
description = "Check if transaction is within 50% of account balance"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Account/Transaction",
|
||||
title = "Same Currency Check",
|
||||
code = "transactionOpt.exists(t => accountOpt.exists(a => t.currency == a.currency))",
|
||||
description = "Check if transaction and account have same currency"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Account/Transaction",
|
||||
title = "Sufficient Funds After Transaction",
|
||||
code = "transactionOpt.exists(t => accountOpt.exists(a => a.balance - t.amount >= 0))",
|
||||
description = "Check if account will have sufficient funds after transaction"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Account/Transaction",
|
||||
title = "Debit from Checking",
|
||||
code = "transactionOpt.exists(t => accountOpt.exists(a => (a.accountType == \"CHECKING\" && t.transactionType.exists(_.contains(\"DEBIT\")))))",
|
||||
description = "Check if debit transaction from checking account"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Bank/Account",
|
||||
title = "Account Belongs to Bank",
|
||||
code = "accountOpt.isDefined && bankOpt.isDefined && accountOpt.get.bankId == bankOpt.get.bankId.value",
|
||||
description = "Check if account belongs to the bank"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Bank/Account",
|
||||
title = "Account Currency Matches Bank Currency",
|
||||
code = "accountOpt.exists(a => bankAttributes.exists(attr => attr.name == \"primary_currency\" && attr.value == a.currency))",
|
||||
description = "Check if account currency matches bank's primary currency"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Transaction Request",
|
||||
title = "Transaction Request for Account",
|
||||
code = "transactionRequestOpt.exists(tr => accountOpt.exists(a => tr.this_account_id.value == a.accountId.value))",
|
||||
description = "Check if transaction request is for this account"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Transaction Request",
|
||||
title = "Transaction Request for Bank",
|
||||
code = "transactionRequestOpt.exists(tr => bankOpt.exists(b => tr.this_bank_id.value == b.bankId.value))",
|
||||
description = "Check if transaction request is for this bank"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Transaction Request",
|
||||
title = "Transaction Amount Matches Charge",
|
||||
code = "transactionOpt.isDefined && transactionRequestOpt.isDefined && transactionOpt.get.amount == transactionRequestOpt.get.charge.value.toDouble",
|
||||
description = "Check if transaction amount matches request charge"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Attribute Comparisons",
|
||||
title = "User and Account Same Tier",
|
||||
code = "userAttributes.exists(ua => ua.name == \"tier\" && accountAttributes.exists(aa => aa.name == \"tier\" && ua.value == aa.value))",
|
||||
description = "Check if user tier matches account tier"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Attribute Comparisons",
|
||||
title = "Customer and Account Same Segment",
|
||||
code = "customerAttributes.exists(ca => ca.name == \"segment\" && accountAttributes.exists(aa => aa.name == \"segment\" && ca.value == aa.value))",
|
||||
description = "Check if customer segment matches account segment"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Attribute Comparisons",
|
||||
title = "Auth User and Account Same Department",
|
||||
code = "authenticatedUserAttributes.exists(ua => ua.name == \"department\" && accountAttributes.exists(aa => aa.name == \"department\" && ua.value == aa.value))",
|
||||
description = "Check if authenticated user department matches account department"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Attribute Comparisons",
|
||||
title = "Transaction Risk Within User Tolerance",
|
||||
code = "transactionAttributes.exists(ta => ta.name == \"risk_score\" && userAttributes.exists(ua => ua.name == \"risk_tolerance\" && ta.value.toInt <= ua.value.toInt))",
|
||||
description = "Check if transaction risk is within user's tolerance"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Cross-Object - Attribute Comparisons",
|
||||
title = "Bank and Customer Same Region",
|
||||
code = "bankAttributes.exists(ba => ba.name == \"region\" && customerAttributes.exists(ca => ca.name == \"region\" && ba.value == ca.value))",
|
||||
description = "Check if bank and customer are in same region"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Multi-Object",
|
||||
title = "Bank Employee with Active Account",
|
||||
code = "authenticatedUser.emailAddress.endsWith(\"@bank.com\") && accountOpt.exists(_.balance > 0) && bankOpt.exists(_.bankId.value == \"gh.29.uk\")",
|
||||
description = "Check if bank employee accessing active account at specific bank"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Multi-Object",
|
||||
title = "Manager Accessing Other User",
|
||||
code = "authenticatedUserAttributes.exists(_.name == \"role\" && _.value == \"manager\") && userOpt.exists(_.userId != authenticatedUser.userId)",
|
||||
description = "Check if manager is accessing another user's data"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Multi-Object",
|
||||
title = "Self or Authorized Delegation with Balance",
|
||||
code = "(onBehalfOfUserOpt.isEmpty || onBehalfOfUserOpt.get.userId == authenticatedUser.userId) && accountOpt.exists(_.balance > 1000)",
|
||||
description = "Check if self-access or authorized delegation with minimum balance"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Multi-Object",
|
||||
title = "Verified User with Optional Delegation",
|
||||
code = "userAttributes.exists(_.name == \"kyc_status\" && _.value == \"verified\") && (onBehalfOfUserOpt.isEmpty || onBehalfOfUserAttributes.exists(_.name == \"authorized\"))",
|
||||
description = "Check if user is KYC verified and delegation is authorized (if present)"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Multi-Object",
|
||||
title = "VIP Customer with Premium Account",
|
||||
code = "customerAttributes.exists(_.name == \"vip_status\" && _.value == \"true\") && accountAttributes.exists(_.name == \"account_tier\" && _.value == \"premium\")",
|
||||
description = "Check if VIP customer has premium account"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Chained Validation",
|
||||
title = "User-Customer-Account-Transaction Chain",
|
||||
code = "userOpt.exists(u => customerOpt.exists(c => c.email == u.emailAddress && accountOpt.exists(a => transactionOpt.exists(t => t.accountId.value == a.accountId.value))))",
|
||||
description = "Validate complete chain from user to customer to account to transaction"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Chained Validation",
|
||||
title = "Bank-Account-Transaction Request Chain",
|
||||
code = "bankOpt.exists(b => accountOpt.exists(a => a.bankId == b.bankId.value && transactionRequestOpt.exists(tr => tr.this_account_id.value == a.accountId.value)))",
|
||||
description = "Validate bank owns account and transaction request is for that account"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Aggregation",
|
||||
title = "Matching Attributes",
|
||||
code = "authenticatedUserAttributes.exists(aua => userAttributes.exists(ua => aua.name == ua.name && aua.value == ua.value))",
|
||||
description = "Check if authenticated user and target user share any matching attributes"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Complex - Aggregation",
|
||||
title = "Allowed Transaction Attributes",
|
||||
code = "transactionAttributes.forall(ta => accountAttributes.exists(aa => aa.name == \"allowed_transaction_\" + ta.name))",
|
||||
description = "Check if all transaction attributes are allowed for this account"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Business Logic - Loan Approval",
|
||||
title = "Credit Score and Balance Check",
|
||||
code = "customerAttributes.exists(ca => ca.name == \"credit_score\" && ca.value.toInt > 650) && accountOpt.exists(_.balance > 5000)",
|
||||
description = "Check if customer has good credit score and sufficient balance for loan"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Business Logic - Wire Transfer",
|
||||
title = "Wire Transfer Authorization",
|
||||
code = "transactionOpt.exists(t => t.amount < 100000 && t.transactionType.exists(_.contains(\"WIRE\"))) && authenticatedUserAttributes.exists(_.name == \"wire_authorized\")",
|
||||
description = "Check if wire transfer is under limit and user is authorized"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Business Logic - Account Closure",
|
||||
title = "Self-Service or Manager Account Closure",
|
||||
code = "accountOpt.exists(a => (a.balance == 0 && userOpt.exists(_.userId == authenticatedUser.userId)) || authenticatedUserAttributes.exists(_.name == \"role\" && _.value == \"manager\"))",
|
||||
description = "Allow account closure if zero balance self-service or manager override"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Business Logic - VIP Processing",
|
||||
title = "VIP Priority Check",
|
||||
code = "(customerAttributes.exists(_.name == \"vip_status\" && _.value == \"true\") || accountAttributes.exists(_.name == \"account_tier\" && _.value == \"platinum\"))",
|
||||
description = "Check if customer or account qualifies for VIP priority processing"
|
||||
),
|
||||
AbacRuleExampleJsonV600(
|
||||
category = "Business Logic - Joint Account",
|
||||
title = "Joint Account Access",
|
||||
code = "accountOpt.exists(a => a.accountHolders.exists(h => h.userId == authenticatedUser.userId || h.emailAddress == authenticatedUser.emailAddress))",
|
||||
description = "Check if authenticated user is one of the account holders"
|
||||
rule_name = "Fraud Analyst High-Risk Transaction Access",
|
||||
rule_code = "authenticatedUserEntitlements.exists(e => e.roleName == \"CanReadTransactionsAtOneBank\") && callContext.exists(c => c.verb.exists(_ == \"GET\") && c.implementedByPartialFunction.exists(_.contains(\"Transaction\"))) && transactionAttributes.exists(ta => ta.name == \"risk_score\" && ta.value.toInt >= 75) && transactionOpt.exists(_.status.exists(_ != \"COMPLETED\"))",
|
||||
description = "Allow users with CanReadTransactionsAtOneBank role to GET high-risk (score ≥75) non-completed transactions",
|
||||
policy = "transaction-access",
|
||||
is_active = true
|
||||
)
|
||||
),
|
||||
available_operators = List(
|
||||
@ -5795,6 +5420,59 @@ trait APIMethods600 {
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
getAbacPolicies,
|
||||
implementedInApiVersion,
|
||||
nameOf(getAbacPolicies),
|
||||
"GET",
|
||||
"/management/abac-policies",
|
||||
"Get ABAC Policies",
|
||||
s"""Get the list of allowed ABAC policy names.
|
||||
|
|
||||
|ABAC rules are organized by policies. Each rule must have at least one policy assigned.
|
||||
|Rules can have multiple policies (comma-separated). This endpoint returns the list of
|
||||
|standardized policy names that should be used when creating or updating rules.
|
||||
|
|
||||
|${userAuthenticationMessage(true)}
|
||||
|
|
||||
|""".stripMargin,
|
||||
EmptyBody,
|
||||
AbacPoliciesJsonV600(
|
||||
policies = List(
|
||||
AbacPolicyJsonV600(
|
||||
policy = "account-access",
|
||||
description = "Rules for controlling access to account information"
|
||||
)
|
||||
)
|
||||
),
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagABAC),
|
||||
Some(List(canGetAbacRule))
|
||||
)
|
||||
|
||||
lazy val getAbacPolicies: OBPEndpoint = {
|
||||
case "management" :: "abac-policies" :: Nil JsonGet _ => {
|
||||
cc => implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
(Full(user), callContext) <- authenticatedAccess(cc)
|
||||
_ <- NewStyle.function.hasEntitlement("", user.userId, canGetAbacRule, callContext)
|
||||
} yield {
|
||||
val policies = Constant.ABAC_POLICIES.map { policy =>
|
||||
AbacPolicyJsonV600(
|
||||
policy = policy,
|
||||
description = Constant.ABAC_POLICY_DESCRIPTIONS.getOrElse(policy, "No description available")
|
||||
)
|
||||
}
|
||||
|
||||
(AbacPoliciesJsonV600(policies), HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
validateAbacRule,
|
||||
implementedInApiVersion,
|
||||
@ -5992,6 +5670,112 @@ trait APIMethods600 {
|
||||
}
|
||||
}
|
||||
|
||||
staticResourceDocs += ResourceDoc(
|
||||
executeAbacPolicy,
|
||||
implementedInApiVersion,
|
||||
nameOf(executeAbacPolicy),
|
||||
"POST",
|
||||
"/management/abac-policies/POLICY/execute",
|
||||
"Execute ABAC Policy",
|
||||
s"""Execute all ABAC rules in a policy to test access control.
|
||||
|
|
||||
|This endpoint executes all active rules that belong to the specified policy.
|
||||
|The policy uses OR logic - access is granted if at least one rule passes.
|
||||
|
|
||||
|This allows you to test a complete policy with specific context (authenticated user, bank, account, transaction, customer, etc.).
|
||||
|
|
||||
|**Documentation:**
|
||||
|- ${Glossary.getGlossaryItemLink("ABAC_Simple_Guide")} - Getting started with ABAC rules
|
||||
|- ${Glossary.getGlossaryItemLink("ABAC_Parameters_Summary")} - Complete list of all 18 parameters
|
||||
|- ${Glossary.getGlossaryItemLink("ABAC_Object_Properties_Reference")} - Detailed property reference
|
||||
|- ${Glossary.getGlossaryItemLink("ABAC_Testing_Examples")} - Testing examples and patterns
|
||||
|
|
||||
|You can provide optional IDs in the request body to test the policy with specific context.
|
||||
|
|
||||
|${userAuthenticationMessage(true)}
|
||||
|
|
||||
|""".stripMargin,
|
||||
ExecuteAbacRuleJsonV600(
|
||||
authenticated_user_id = Some("c7b6cb47-cb96-4441-8801-35b57456753a"),
|
||||
on_behalf_of_user_id = Some("a3b5c123-1234-5678-9012-fedcba987654"),
|
||||
user_id = Some("c7b6cb47-cb96-4441-8801-35b57456753a"),
|
||||
bank_id = Some("gh.29.uk"),
|
||||
account_id = Some("8ca8a7e4-6d02-48e3-a029-0b2bf89de9f0"),
|
||||
view_id = Some("owner"),
|
||||
transaction_request_id = Some("123456"),
|
||||
transaction_id = Some("abc123"),
|
||||
customer_id = Some("customer-id-123")
|
||||
),
|
||||
AbacRuleResultJsonV600(
|
||||
result = true
|
||||
),
|
||||
List(
|
||||
UserNotLoggedIn,
|
||||
UserHasMissingRoles,
|
||||
InvalidJsonFormat,
|
||||
UnknownError
|
||||
),
|
||||
List(apiTagABAC),
|
||||
Some(List(canExecuteAbacRule))
|
||||
)
|
||||
|
||||
lazy val executeAbacPolicy: OBPEndpoint = {
|
||||
case "management" :: "abac-policies" :: policy :: "execute" :: Nil JsonPost json -> _ => {
|
||||
cc => implicit val ec = EndpointContext(Some(cc))
|
||||
for {
|
||||
(Full(user), callContext) <- authenticatedAccess(cc)
|
||||
_ <- NewStyle.function.hasEntitlement("", user.userId, canExecuteAbacRule, callContext)
|
||||
execJson <- NewStyle.function.tryons(s"$InvalidJsonFormat", 400, callContext) {
|
||||
json.extract[ExecuteAbacRuleJsonV600]
|
||||
}
|
||||
|
||||
// Verify the policy exists
|
||||
_ <- Future {
|
||||
if (Constant.ABAC_POLICIES.contains(policy)) {
|
||||
Full(true)
|
||||
} else {
|
||||
Failure(s"Policy not found: $policy. Available policies: ${Constant.ABAC_POLICIES.mkString(", ")}")
|
||||
}
|
||||
} map {
|
||||
unboxFullOrFail(_, callContext, s"Invalid ABAC Policy: $policy", 404)
|
||||
}
|
||||
|
||||
// Execute the policy with IDs - object fetching happens internally
|
||||
// authenticatedUserId: can be provided in request (for testing) or defaults to actual authenticated user
|
||||
// onBehalfOfUserId: optional delegation - acting on behalf of another user
|
||||
// userId: the target user being evaluated (defaults to authenticated user)
|
||||
effectiveAuthenticatedUserId = execJson.authenticated_user_id.getOrElse(user.userId)
|
||||
|
||||
result <- Future {
|
||||
val resultBox = AbacRuleEngine.executeRulesByPolicy(
|
||||
policy = policy,
|
||||
authenticatedUserId = effectiveAuthenticatedUserId,
|
||||
onBehalfOfUserId = execJson.on_behalf_of_user_id,
|
||||
userId = execJson.user_id,
|
||||
callContext = callContext.getOrElse(cc),
|
||||
bankId = execJson.bank_id,
|
||||
accountId = execJson.account_id,
|
||||
viewId = execJson.view_id,
|
||||
transactionId = execJson.transaction_id,
|
||||
transactionRequestId = execJson.transaction_request_id,
|
||||
customerId = execJson.customer_id
|
||||
)
|
||||
|
||||
resultBox match {
|
||||
case Full(allowed) =>
|
||||
AbacRuleResultJsonV600(result = allowed)
|
||||
case Failure(msg, _, _) =>
|
||||
AbacRuleResultJsonV600(result = false)
|
||||
case Empty =>
|
||||
AbacRuleResultJsonV600(result = false)
|
||||
}
|
||||
}
|
||||
} yield {
|
||||
(result, HttpCode.`200`(callContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================================================
|
||||
// USER ATTRIBUTES v6.0.0 - Consistent with other entity attributes
|
||||
// ============================================================================================================
|
||||
|
||||
@ -380,6 +380,7 @@ case class CreateAbacRuleJsonV600(
|
||||
rule_name: String,
|
||||
rule_code: String,
|
||||
description: String,
|
||||
policy: String,
|
||||
is_active: Boolean
|
||||
)
|
||||
|
||||
@ -387,6 +388,7 @@ case class UpdateAbacRuleJsonV600(
|
||||
rule_name: String,
|
||||
rule_code: String,
|
||||
description: String,
|
||||
policy: String,
|
||||
is_active: Boolean
|
||||
)
|
||||
|
||||
@ -396,6 +398,7 @@ case class AbacRuleJsonV600(
|
||||
rule_code: String,
|
||||
is_active: Boolean,
|
||||
description: String,
|
||||
policy: String,
|
||||
created_by_user_id: String,
|
||||
updated_by_user_id: String
|
||||
)
|
||||
@ -459,10 +462,11 @@ case class AbacObjectTypeJsonV600(
|
||||
)
|
||||
|
||||
case class AbacRuleExampleJsonV600(
|
||||
category: String,
|
||||
title: String,
|
||||
code: String,
|
||||
description: String
|
||||
rule_name: String,
|
||||
rule_code: String,
|
||||
description: String,
|
||||
policy: String,
|
||||
is_active: Boolean
|
||||
)
|
||||
|
||||
case class AbacRuleSchemaJsonV600(
|
||||
@ -473,6 +477,15 @@ case class AbacRuleSchemaJsonV600(
|
||||
notes: List[String]
|
||||
)
|
||||
|
||||
case class AbacPolicyJsonV600(
|
||||
policy: String,
|
||||
description: String
|
||||
)
|
||||
|
||||
case class AbacPoliciesJsonV600(
|
||||
policies: List[AbacPolicyJsonV600]
|
||||
)
|
||||
|
||||
object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
|
||||
|
||||
def createRedisCallCountersJson(
|
||||
@ -1086,6 +1099,7 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable {
|
||||
rule_code = rule.ruleCode,
|
||||
is_active = rule.isActive,
|
||||
description = rule.description,
|
||||
policy = rule.policy,
|
||||
created_by_user_id = rule.createdByUserId,
|
||||
updated_by_user_id = rule.updatedByUserId
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user