diff --git a/.github/Dockerfile_PreBuild_Jmx b/.github/Dockerfile_PreBuild_Jmx new file mode 100644 index 000000000..1fceef430 --- /dev/null +++ b/.github/Dockerfile_PreBuild_Jmx @@ -0,0 +1,9 @@ +FROM jetty:9.4-jdk11-alpine + +# Copy OBP source code +# Copy build artifact (.war file) into jetty from 'maven' stage. +COPY /jmx_prometheus_javaagent-0.20.0.jar /var/lib/jetty/jmx_prometheus_javaagent-0.20.0.jar +COPY /.github/jmx_exporter.config /var/lib/jetty/prometheus_config.yml +COPY /obp-api/target/obp-api-1.*.war /var/lib/jetty/webapps/ROOT.war + +CMD ["java -jar $JETTY_HOME/start.jar -javaagent:$JETTY_BASE/jmx_prometheus_javaagent-0.20.0.jar=8090:$JETTY_BASE/prometheus_config.yml"] \ No newline at end of file diff --git a/.github/workflows/build_container_develop_branch.yml b/.github/workflows/build_container_develop_branch.yml new file mode 100644 index 000000000..ddbeb6b3a --- /dev/null +++ b/.github/workflows/build_container_develop_branch.yml @@ -0,0 +1,81 @@ +name: Build and publish container develop + +# read-write repo token +# access to secrets +on: + workflow_run: + workflows: [build maven artifact] + branches: + - 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@v3 + - name: 'Download artifact' + uses: actions/github-script@v3.1.0 + with: + script: | + var artifacts = await github.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "push" + })[0]; + var download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/push.zip', Buffer.from(download.data)); + - run: unzip push.zip + + - name: prepare the artifact + run: | + mkdir -p obp-api/target/ + cp obp-api-1.10.1.war obp-api/target/obp-api-1.10.1.war + + - 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@main + + - 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}}" + + + diff --git a/.github/workflows/build_container_non_develop_branch.yml b/.github/workflows/build_container_non_develop_branch.yml new file mode 100644 index 000000000..b0837465d --- /dev/null +++ b/.github/workflows/build_container_non_develop_branch.yml @@ -0,0 +1,82 @@ +name: Build and publish container non develop + +# read-write repo token +# access to secrets +on: + workflow_run: + workflows: [build maven artifact] + branches: + - '*' + - '!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@v3 + - name: 'Download artifact' + uses: actions/github-script@v3.1.0 + with: + script: | + var artifacts = await github.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "push" + })[0]; + var download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/push.zip', Buffer.from(download.data)); + - run: unzip push.zip + + - name: prepare the artifact + run: | + mkdir -p obp-api/target/ + cp obp-api-1.10.1.war obp-api/target/obp-api-1.10.1.war + + - 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@main + + - 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}}" + + + diff --git a/.github/workflows/build_contributer_container.yml b/.github/workflows/build_contributer_container.yml new file mode 100644 index 000000000..d64fb7b18 --- /dev/null +++ b/.github/workflows/build_contributer_container.yml @@ -0,0 +1,57 @@ +name: Build and publish commiter container + +# read-write repo token +# access to secrets +on: + workflow_run: + workflows: [Build on Pull Request] + types: + - completed + +env: + ## Sets environment variable + DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }} + +jobs: + upload: + runs-on: ubuntu-latest + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + steps: + - uses: actions/checkout@v3 + - name: 'Download artifact' + uses: actions/github-script@v3.1.0 + with: + script: | + var artifacts = await github.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{github.event.workflow_run.id }}, + }); + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + return artifact.name == "pr" + })[0]; + var download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + var fs = require('fs'); + fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data)); + - run: unzip pr.zip + - name: Get user from file + run: echo "USER_NAME=$(cat UN)" >> $GITHUB_ENV + + - name: prepare the artifact + run: | + mkdir -p obp-api/target/ + cp obp-api-1.10.1.war obp-api/target/obp-api-1.10.1.war + + - 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 }}/obp-api-${{ env.USER_NAME }}:$GITHUB_SHA --tag docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/obp-api-${{ env.USER_NAME }}:latest + docker push docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/obp-api-${{ env.USER_NAME }} --all-tags + echo docker done diff --git a/.github/workflows/build_jmx_container.yml b/.github/workflows/build_jmx_container.yml new file mode 100644 index 000000000..93a27d92d --- /dev/null +++ b/.github/workflows/build_jmx_container.yml @@ -0,0 +1,96 @@ +name: Build and publish jmx container develop + +# read-write repo token +# access to secrets +on: workflow_dispatch + +env: + ## Sets environment variable + DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }} + DOCKER_HUB_REPOSITORY: obp-api + + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: 'Download artifact' + uses: actions/github-script@v3.1.0 + with: + script: | + var matchRun = workflowRuns.data.workflow_runs.filter((run) => { + return run.head_sha == context.sha + var workflowRuns = await github.actions.listWorkflowRunsForRepo({ + })[0]; + owner: context.repo.owner, + repo: context.repo.repo, + }); + var artifacts = await github.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + var matchRun = workflowRuns.data.workflow_runs.filter((run) => { + return run.head_sha == context.sha + })[0]; + run_id: matchRun.id, + }); + if (!matchRun) { + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + console.log('No matching workflow run found for this commit'); + return; + } + return artifact.name == "push" + })[0]; + var artifacts = await github.actions.listWorkflowRunArtifacts({ + var download = await github.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + owner: context.repo.owner, + repo: context.repo.repo, + run_id: matchRun.id, + artifact_id: matchArtifact.id, + }); + archive_format: 'zip', + }); + var matchArtifact = artifacts.data.artifacts.filter((artifact) => { + var fs = require('fs'); + return artifact.name == "push" + })[0]; + if (!matchArtifact) { + fs.writeFileSync('${{github.workspace}}/push.zip', Buffer.from(download.data)); + - run: unzip push.zip + + - name: prepare the artifact + run: | + mkdir -p obp-api/target/ + cp push/obp-api-1.*.war obp-api/target/obp-api-1.10.1.war + + - 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 push docker.io/${{ env.DOCKER_HUB_ORGANIZATION }}/${{ env.DOCKER_HUB_REPOSITORY }} --all-tags + echo docker done + + - uses: sigstore/cosign-installer@main + + - 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}}" + + + diff --git a/.github/workflows/build_package.yml b/.github/workflows/build_package.yml index 9fac34f8a..56b0c50aa 100644 --- a/.github/workflows/build_package.yml +++ b/.github/workflows/build_package.yml @@ -1,11 +1,6 @@ -name: build and publish container +name: build maven artifact on: [push] -env: - ## Sets environment variable - DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION }} - DOCKER_HUB_REPOSITORY: obp-api - jobs: build: @@ -73,33 +68,18 @@ jobs: echo consents.allowed=true >> obp-api/src/main/resources/props/test.default.props MAVEN_OPTS="-Xmx3G -Xss2m" mvn clean package -Pprod - - name: Build the Docker image + + - name: Save .war artifact 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@main - - - 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}}" + mkdir -p ./push + cp obp-api/target/obp-api-1.*.war ./push/ + - uses: actions/upload-artifact@v2 + with: + name: push + path: push/ + + + diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml new file mode 100644 index 000000000..eb3910655 --- /dev/null +++ b/.github/workflows/build_pull_request.yml @@ -0,0 +1,89 @@ +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@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: maven + - name: Build with Maven + run: | + 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 kafka.akka.timeout = 9 >> obp-api/src/main/resources/props/test.default.props + echo remotedata.timeout = 10 >> 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 + - name: Save user name and .war artifact + run: | + mkdir -p ./pr + echo ${{ github.event.pull_request.user.login }} > ./pr/UN + cp obp-api/target/obp-api-1.10.1.war ./pr/obp-api-1.10.1.war + - uses: actions/upload-artifact@v2 + with: + name: pr + path: pr/ + + + diff --git a/.github/workflows/run_trivy.yml b/.github/workflows/run_trivy.yml index a8a50366c..548cd92ad 100644 --- a/.github/workflows/run_trivy.yml +++ b/.github/workflows/run_trivy.yml @@ -2,7 +2,9 @@ name: scan container image on: workflow_run: - workflows: [build and publish container] + workflows: + - Build and publish container develop + - Build and publish container non develop types: - completed env: diff --git a/obp-api/pom.xml b/obp-api/pom.xml index 4b8194138..3fdc02716 100644 --- a/obp-api/pom.xml +++ b/obp-api/pom.xml @@ -58,7 +58,7 @@ ch.qos.logback logback-classic - 1.2.3 + 1.2.13 net.liftweb @@ -114,7 +114,7 @@ org.postgresql postgresql - 42.4.3 + 42.4.4 @@ -212,7 +212,7 @@ org.elasticsearch elasticsearch - 8.8.1 + 8.14.0 @@ -266,7 +266,7 @@ org.apache.commons commons-compress - 1.23.0 + 1.26.0 com.twitter @@ -299,7 +299,7 @@ com.nimbusds nimbus-jose-jwt - 9.31 + 9.37.2 com.github.OpenBankProject diff --git a/obp-api/src/main/resources/i18n/lift-core.properties b/obp-api/src/main/resources/i18n/lift-core.properties index 3a325f7be..4f7d69d1f 100644 --- a/obp-api/src/main/resources/i18n/lift-core.properties +++ b/obp-api/src/main/resources/i18n/lift-core.properties @@ -385,4 +385,16 @@ your.secret.link.is.not.valid = Looks like the invitation link is invalid. Still privacy_policy_checkbox_text =I agree to the above Privacy Policy privacy_policy_checkbox_label = Privacy Policy terms_and_conditions_checkbox_text = I agree to the above Terms and Conditions -terms_and_conditions_checkbox_label = Terms and Conditions \ No newline at end of file +terms_and_conditions_checkbox_label = Terms and Conditions + +# Terms and conditions page +outdated.terms.button.skip = Skip +outdated.terms.button.accept = Accept +outdated.terms.warning.1 = Dear +outdated.terms.warning.2 = . We have updated our terms since you last agreed to them! Please review the text and agree if you agree! + +# Privacy policy page +outdated.policy.button.skip = Skip +outdated.policy.button.accept = Accept +outdated.policy.warning.1 = Dear +outdated.policy.warning.2 = . We have updated our policy since you last agreed to them! Please review the text and agree if you agree! \ No newline at end of file diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 0498f5ad7..8a1860ef4 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -881,7 +881,11 @@ database_messages_scheduler_interval=3600 # -- Consents --------------------------------------------- # In case isn't defined default value is "false" # consents.allowed=true -# consumer_validation_method_for_consent=CONSUMER_KEY_VALUE +# +# In order to pin a consent to a consumer we can use the property consumer_validation_method_for_consent +# Possibile values are: CONSUMER_CERTIFICATE, CONSUMER_KEY_VALUE, NONE +# consumer_validation_method_for_consent=CONSUMER_CERTIFICATE +# # consents.max_time_to_live=3600 # In case isn't defined default value is "false" # consents.sca.enabled=true diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 753f26f4b..4a865039d 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -63,6 +63,7 @@ import code.connectormethod.ConnectorMethod import code.consent.{ConsentRequest, MappedConsent} import code.consumer.Consumers import code.context.{MappedConsentAuthContext, MappedUserAuthContext, MappedUserAuthContextUpdate} +import code.counterpartylimit.CounterpartyLimit import code.crm.MappedCrmEvent import code.customer.internalMapping.MappedCustomerIdMapping import code.customer.{MappedCustomer, MappedCustomerMessage} @@ -129,7 +130,7 @@ import code.util.Helper.{MdcLoggable, ObpS, SILENCE_IS_GOLDEN} import code.util.{Helper, HydraUtil} import code.validation.JsonSchemaValidation import code.views.Views -import code.views.system.{AccountAccess, ViewDefinition} +import code.views.system.{AccountAccess, ViewDefinition, ViewPermission} import code.webhook.{BankAccountNotificationWebhook, MappedAccountWebhook, SystemAccountNotificationWebhook} import code.webuiprops.WebUiProps import com.openbankproject.commons.model.ErrorMessage @@ -143,7 +144,7 @@ import net.liftweb.http.LiftRules.DispatchPF import net.liftweb.http._ import net.liftweb.http.provider.HTTPCookie import net.liftweb.json.Extraction -import net.liftweb.mapper.{DefaultConnectionIdentifier=>_, _} +import net.liftweb.mapper.{DefaultConnectionIdentifier => _, _} import net.liftweb.sitemap.Loc._ import net.liftweb.sitemap._ import net.liftweb.util.Helpers._ @@ -1086,7 +1087,9 @@ object ToSchemify { DynamicMessageDoc, EndpointTag, ProductFee, - UserInitAction + ViewPermission, + UserInitAction, + CounterpartyLimit ) // start grpc server diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 549dd5c70..6a1c91646 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -36,7 +36,7 @@ import com.openbankproject.commons.util.{ApiVersion, FieldNameApiVersions, Refle import net.liftweb.json import java.net.URLEncoder -import code.api.v5_1_0.{AtmsJsonV510, _} +import code.api.v5_1_0.{AtmsJsonV510, CustomViewJsonV510, _} import code.endpointMapping.EndpointMappingCommons import net.liftweb.json.Extraction @@ -52,6 +52,7 @@ import scala.collection.immutable.List */ object SwaggerDefinitionsJSON { + implicit def convertStringToBoolean(value:String) = value.toBoolean lazy val regulatedEntitiesJsonV510: RegulatedEntitiesJsonV510 = RegulatedEntitiesJsonV510(List(regulatedEntityJsonV510)) lazy val regulatedEntityJsonV510: RegulatedEntityJsonV510 = RegulatedEntityJsonV510( @@ -314,18 +315,48 @@ object SwaggerDefinitionsJSON { "can_revoke_access_to_custom_views" ) + val createCustomViewJson = CreateCustomViewJson( + name = viewNameExample.value, + description= viewDescriptionExample.value, + metadata_view= metadataViewExample.value, + is_public = isPublicExample.value, + which_alias_to_use = whichAliasToUseExample.value, + hide_metadata_if_alias_used = hideMetadataIfAliasUsedExample.value.toBoolean, + allowed_permissions= allowedActionsV500, + ) + + val customViewJsonV510 = CustomViewJsonV510( + id = viewIdExample.value, + name = viewNameExample.value, + description = viewDescriptionExample.value, + metadata_view = metadataViewExample.value, + is_public = isPublicExample.value, + alias = whichAliasToUseExample.value, + hide_metadata_if_alias_used = hideMetadataIfAliasUsedExample.value.toBoolean, + allowed_permissions = allowedActionsV500 + ) + val createSystemViewJsonV500 = CreateViewJsonV500( - name = "_test", - description = "This view is for family", - metadata_view ="_test", - is_public = false, - which_alias_to_use = "family", - hide_metadata_if_alias_used = false, + name = viewNameExample.value, + description = viewDescriptionExample.value, + metadata_view =viewDescriptionExample.value, + is_public = isPublicExample.value, + which_alias_to_use = whichAliasToUseExample.value, + hide_metadata_if_alias_used = hideMetadataIfAliasUsedExample.value.toBoolean, allowed_actions = allowedActionsV500, // Version 5.0.0 can_grant_access_to_views = Some(List(Constant.SYSTEM_OWNER_VIEW_ID)), can_revoke_access_to_views = Some(List(Constant.SYSTEM_OWNER_VIEW_ID)) ) + + val updateCustomViewJson = UpdateCustomViewJson( + description = viewDescriptionExample.value, + metadata_view = metadataViewExample.value, + is_public = isPublicExample.value, + which_alias_to_use = whichAliasToUseExample.value, + hide_metadata_if_alias_used = hideMetadataIfAliasUsedExample.value.toBoolean, + allowed_permissions = allowedActionsV500 + ) val updateViewJsonV300 = UpdateViewJsonV300( description = "this is for family", @@ -5396,6 +5427,27 @@ object SwaggerDefinitionsJSON { phone = phoneExample.value, ) + val postCounterpartyLimitV510 = PostCounterpartyLimitV510( + max_single_amount = maxSingleAmountExample.value.toInt, + max_monthly_amount = maxMonthlyAmountExample.value.toInt, + max_number_of_monthly_transactions = maxNumberOfMonthlyTransactionsExample.value.toInt, + max_yearly_amount = maxYearlyAmountExample.value.toInt, + max_number_of_yearly_transactions = maxNumberOfYearlyTransactionsExample.value.toInt + ) + + val counterpartyLimitV510 = CounterpartyLimitV510( + counterparty_limit_id = counterpartyLimitIdExample.value, + bank_id = bankIdExample.value, + account_id = accountIdExample.value, + view_id = viewIdExample.value, + counterparty_id = counterpartyIdExample.value, + max_single_amount = maxSingleAmountExample.value.toInt, + max_monthly_amount = maxMonthlyAmountExample.value.toInt, + max_number_of_monthly_transactions = maxNumberOfMonthlyTransactionsExample.value.toInt, + max_yearly_amount = maxYearlyAmountExample.value.toInt, + max_number_of_yearly_transactions = maxNumberOfYearlyTransactionsExample.value.toInt + ) + val atmsJsonV510 = AtmsJsonV510( atms = List(atmJsonV510) ) diff --git a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala index 0c7d68ea6..b2fe2b1f2 100644 --- a/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala +++ b/obp-api/src/main/scala/code/api/berlin/group/v1_3/AccountInformationServiceAISApi.scala @@ -1,7 +1,6 @@ package code.api.builder.AccountInformationServiceAISApi import java.text.SimpleDateFormat - import code.api.APIFailureNewStyle import code.api.Constant.{SYSTEM_READ_ACCOUNTS_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID, SYSTEM_READ_TRANSACTIONS_BERLIN_GROUP_VIEW_ID} import code.api.berlin.group.v1_3.JSONFactory_BERLIN_GROUP_1_3.{PostConsentResponseJson, _} @@ -12,6 +11,7 @@ import code.api.util.APIUtil.{passesPsd2Aisp, _} import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ import code.api.util.NewStyle.HttpCode +import code.api.util.newstyle.BalanceNewStyle import code.api.util.{APIUtil, ApiTag, CallContext, Consent, ExampleValue, NewStyle} import code.bankconnectors.Connector import code.consent.{ConsentStatus, Consents} @@ -367,7 +367,7 @@ The account-id is constant at least throughout the lifecycle of a given consent. _ <- passesPsd2Aisp(callContext) (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(accountId, callContext) _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) - (accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext) + (accountBalances, callContext)<- BalanceNewStyle.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext) } yield { (JSONFactory_BERLIN_GROUP_1_3.createAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext)) } @@ -482,7 +482,7 @@ This account-id then can be retrieved by the _ <- passesPsd2Aisp(callContext) (account: BankAccount, callContext) <- NewStyle.function.getBankAccountByAccountId(AccountId(accountId), callContext) _ <- checkAccountAccess(ViewId(SYSTEM_READ_BALANCES_BERLIN_GROUP_VIEW_ID), u, account, callContext) - (accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext) + (accountBalances, callContext)<- BalanceNewStyle.getBankAccountBalances(BankIdAccountId(account.bankId,account.accountId), callContext) } yield { (JSONFactory_BERLIN_GROUP_1_3.createCardAccountBalanceJSON(account, accountBalances), HttpCode.`200`(callContext)) } diff --git a/obp-api/src/main/scala/code/api/util/APIUtil.scala b/obp-api/src/main/scala/code/api/util/APIUtil.scala index eb9c71899..74f57dfa9 100644 --- a/obp-api/src/main/scala/code/api/util/APIUtil.scala +++ b/obp-api/src/main/scala/code/api/util/APIUtil.scala @@ -122,6 +122,7 @@ import code.api.v2_1_0.OBPAPI2_1_0.Implementations2_1_0 import code.api.v2_2_0.OBPAPI2_2_0.Implementations2_2_0 import code.etag.MappedETag import code.users.Users +import code.views.system.{AccountAccess, ViewDefinition} import net.liftweb.mapper.By import scala.collection.mutable @@ -1695,6 +1696,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ private val isNeedCheckView = errorResponseBodies.contains($UserNoPermissionAccessView) && requestUrlPartPath.contains("BANK_ID") && requestUrlPartPath.contains("ACCOUNT_ID") && requestUrlPartPath.contains("VIEW_ID") + private val isNeedCheckCounterparty = errorResponseBodies.contains($CounterpartyNotFoundByCounterpartyId) && requestUrlPartPath.contains("COUNTERPARTY_ID") + private val reversedRequestUrl = requestUrlPartPath.reverse def getPathParams(url: List[String]): Map[String, String] = reversedRequestUrl.zip(url.reverse) collect { @@ -1774,6 +1777,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ Future.successful(null.asInstanceOf[View]) } } + + def checkCounterparty(counterpartyId: Option[CounterpartyId], callContext: Option[CallContext]): OBPReturnType[CounterpartyTrait] = { + if(isNeedCheckCounterparty && counterpartyId.isDefined) { + checkCounterpartyFun(counterpartyId.get)(callContext) + } else { + Future.successful(null.asInstanceOf[CounterpartyTrait] -> callContext) + } + } // reset connectorMethods { val checkerFunctions = mutable.ListBuffer[PartialFunction[_, _]]() @@ -1794,6 +1805,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ if (isNeedCheckView) { checkerFunctions += checkViewFun } + if (isNeedCheckCounterparty) { + checkerFunctions += checkCounterpartyFun + } val addedMethods: List[String] = checkerFunctions.toList.flatMap(getDependentConnectorMethods(_)) .map(value =>("obp." +value).intern()) @@ -1840,6 +1854,7 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ val bankId = pathParams.get("BANK_ID").map(BankId(_)) val accountId = pathParams.get("ACCOUNT_ID").map(AccountId(_)) val viewId = pathParams.get("VIEW_ID").map(ViewId(_)) + val counterpartyId = pathParams.get("COUNTERPARTY_ID").map(CounterpartyId(_)) val request: Box[Req] = S.request val session: Box[LiftSession] = S.session @@ -1850,7 +1865,8 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ * 2. check bankId * 3. roles check * 4. check accountId - * 5. view + * 5. view access + * 6. check counterpartyId * * A Bank MUST be checked before Roles. * In opposite case we get next paradox: @@ -1877,6 +1893,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ // check user access permission of this viewId corresponding view view <- checkView(viewId, bankId, accountId, boxUser, callContext) + + counterparty <- checkCounterparty(counterpartyId, callContext) + } yield { val newCallContext = if(boxUser.isDefined) callContext.map(_.copy(user=boxUser)) else callContext @@ -2970,8 +2989,14 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ } else if (APIUtil.hasConsentJWT(reqHeaders)) { // Open Bank Project's Consent val consentValue = APIUtil.getConsentJWT(reqHeaders) Consent.getConsentJwtValueByConsentId(consentValue.getOrElse("")) match { - case Some(jwt) => // JWT value obtained via "Consent-Id" request header - Consent.applyRules(Some(jwt), cc) + case Some(consent) => // JWT value obtained via "Consent-Id" request header + Consent.applyRules( + Some(consent.jsonWebToken), + // Note: At this point we are getting the Consumer from the Consumer in the Consent. + // This may later be cross checked via the value in consumer_validation_method_for_consent. + // Get the source of truth for Consumer (e.g. CONSUMER_CERTIFICATE) as early as possible. + cc.copy(consumer = Consent.getCurrentConsumerViaMtls(callContext = cc)) + ) case _ => JwtUtil.checkIfStringIsJWTValue(consentValue.getOrElse("")).isDefined match { case true => // It's JWT obtained via "Consent-JWT" request header @@ -4225,6 +4250,9 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ private val checkViewFun: PartialFunction[ViewId, (BankIdAccountId, Option[User], Option[CallContext]) => Future[View]] = { case x => NewStyle.function.checkViewAccessAndReturnView(x, _, _, _) } + private val checkCounterpartyFun: PartialFunction[CounterpartyId, Option[CallContext] => OBPReturnType[CounterpartyTrait]] = { + case x => NewStyle.function.getCounterpartyByCounterpartyId(x, _) + } // cache for method -> called obp methods: // (className, methodName, signature) -> List[(className, methodName, signature)] @@ -4925,4 +4953,26 @@ object APIUtil extends MdcLoggable with CustomJsonFormats{ else UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount + s"Current source viewId(${sourceViewId.value}) and target viewId (${targetViewId.value})" + + def intersectAccountAccessAndView(accountAccesses: List[AccountAccess], views: List[View]): List[BankIdAccountId] = { + val intersectedViewIds = accountAccesses.map(item => item.view_id.get) + .intersect(views.map(item => item.viewId.value)).distinct // Join view definition and account access via view_id + accountAccesses + .filter(i => intersectedViewIds.contains(i.view_id.get)) + .map(item => BankIdAccountId(BankId(item.bank_id.get), AccountId(item.account_id.get))) + .distinct // List pairs (bank_id, account_id) + } + + //get all the permission Pair from one record, eg: + //List("can_see_transaction_this_bank_account","can_see_transaction_requests"....) + //Note, do not contain can_revoke_access_to_views and can_grant_access_to_views permission yet. + def getViewPermissions(view: ViewDefinition) = view.allFields.map(x => (x.name, x.get)) + .filter(pair =>pair._2.isInstanceOf[Boolean]) + .filter(pair => pair._1.startsWith("can")) + .filter(pair => pair._2.equals(true)) + .map(pair => + StringHelpers.snakify(pair._1) + .dropRight(1) //Remove the "_" in the end, eg canCreateStandingOrder_ --> canCreateStandingOrder + ).toSet + } \ No newline at end of file diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index ce37df854..238ac379d 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -73,6 +73,7 @@ object ApiTag { val apiTagWebUiProps = ResourceDocTag("WebUi-Props") val apiTagEndpointMapping = ResourceDocTag("Endpoint-Mapping") val apiTagRateLimits = ResourceDocTag("Rate-Limits") + val apiTagCounterpartyLimits = ResourceDocTag("Counterparty-Limits") val apiTagApiCollection = ResourceDocTag("Api-Collection") diff --git a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala index 86949f8dd..e78704fe3 100644 --- a/obp-api/src/main/scala/code/api/util/ConsentUtil.scala +++ b/obp-api/src/main/scala/code/api/util/ConsentUtil.scala @@ -15,6 +15,7 @@ import code.context.{ConsentAuthContextProvider, UserAuthContextProvider} import code.entitlement.Entitlement import code.model.Consumer import code.users.Users +import code.util.Helper.MdcLoggable import code.util.HydraUtil import code.views.Views import com.nimbusds.jwt.JWTClaimsSet @@ -25,7 +26,7 @@ import net.liftweb.http.provider.HTTPParam import net.liftweb.json.JsonParser.ParseException import net.liftweb.json.{Extraction, MappingException, compactRender, parse} import net.liftweb.mapper.By -import net.liftweb.util.ControlHelpers +import net.liftweb.util.{ControlHelpers, Props} import sh.ory.hydra.model.OAuth2TokenIntrospection import scala.collection.immutable.{List, Nil} @@ -104,7 +105,7 @@ case class Consent(createdByUserId: String, } } -object Consent { +object Consent extends MdcLoggable { final lazy val challengeAnswerAtTestEnvironment = "123" @@ -118,6 +119,20 @@ object Consent { case _ => None } } + /** + * Purpose of this helper function is to get the Consumer via MTLS info i.e. PEM certificate. + * @return the boxed Consumer + */ + def getCurrentConsumerViaMtls(callContext: CallContext): Box[Consumer] = { + val clientCert: String = APIUtil.`getPSD2-CERT`(callContext.requestHeaders) + .getOrElse(SecureRandomUtil.csprng.nextLong().toString) + def removeBreakLines(input: String) = input + .replace("\n", "") + .replace("\r", "") + Consumers.consumers.vend.getConsumerByPemCertificate(clientCert).or( + Consumers.consumers.vend.getConsumerByPemCertificate(removeBreakLines(clientCert)) + ) + } private def verifyHmacSignedJwt(jwtToken: String, c: MappedConsent): Boolean = { JwtUtil.verifyHmacSignedJwt(jwtToken, c.secret) @@ -126,7 +141,11 @@ object Consent { private def checkConsumerIsActiveAndMatched(consent: ConsentJWT, callContext: CallContext): Box[Boolean] = { Consumers.consumers.vend.getConsumerByConsumerId(consent.aud) match { case Full(consumerFromConsent) if consumerFromConsent.isActive.get == true => // Consumer is active - APIUtil.getPropsValue(nameOfProperty = "consumer_validation_method_for_consent", defaultValue = "CONSUMER_KEY_VALUE") match { + val validationMetod = APIUtil.getPropsValue(nameOfProperty = "consumer_validation_method_for_consent", defaultValue = "CONSUMER_CERTIFICATE") + if(validationMetod != "CONSUMER_CERTIFICATE" && Props.mode == Props.RunModes.Production) { + logger.warn(s"consumer_validation_method_for_consent is not set to CONSUMER_CERTIFICATE! The current value is: ${validationMetod}") + } + validationMetod match { case "CONSUMER_KEY_VALUE" => val requestHeaderConsumerKey = getConsumerKey(callContext.requestHeaders) requestHeaderConsumerKey match { @@ -272,7 +291,7 @@ object Consent { if (result.forall(_ == "Added")) Full(user) else Failure("Cannot add permissions to the user with id: " + user.userId) } - private def hasConsentInternalOldStyle(consentIdAsJwt: String, calContext: CallContext): Box[User] = { + private def applyConsentRulesCommonOldStyle(consentIdAsJwt: String, calContext: CallContext): Box[User] = { implicit val dateFormats = CustomJsonFormats.formats def applyConsentRules(consent: ConsentJWT): Box[User] = { @@ -316,7 +335,7 @@ object Consent { } } - private def hasConsentCommon(consentAsJwt: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { + private def applyConsentRulesCommon(consentAsJwt: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { implicit val dateFormats = CustomJsonFormats.formats def applyConsentRules(consent: ConsentJWT): Future[(Box[User], Option[CallContext])] = { @@ -351,13 +370,16 @@ object Consent { case Full(jsonAsString) => try { val consent = net.liftweb.json.parse(jsonAsString).extract[ConsentJWT] - checkConsent(consent, consentAsJwt, callContext) match { // Check is it Consent-JWT expired + // Set Consumer into Call Context + val consumer = getCurrentConsumerViaMtls(callContext) + val updatedCallContext = callContext.copy(consumer = consumer) + checkConsent(consent, consentAsJwt, updatedCallContext) match { // Check is it Consent-JWT expired case (Full(true)) => // OK applyConsentRules(consent) case failure@Failure(_, _, _) => // Handled errors - Future(failure, Some(callContext)) + Future(failure, Some(updatedCallContext)) case _ => // Unexpected errors - Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(callContext)) + Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(updatedCallContext)) } } catch { // Possible exceptions case e: ParseException => Future(Failure("ParseException: " + e.getMessage), Some(callContext)) @@ -371,27 +393,20 @@ object Consent { } } - private def hasConsentOldStyle(consentIdAsJwt: String, callContext: CallContext): (Box[User], CallContext) = { - (hasConsentInternalOldStyle(consentIdAsJwt, callContext), callContext) - } - private def hasConsent(consentAsJwt: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { - hasConsentCommon(consentAsJwt, callContext) - } - def applyRules(consentJwt: Option[String], callContext: CallContext): Future[(Box[User], Option[CallContext])] = { val allowed = APIUtil.getPropsAsBoolValue(nameOfProperty="consents.allowed", defaultValue=false) (consentJwt, allowed) match { - case (Some(consentId), true) => hasConsent(consentId, callContext) + case (Some(jwt), true) => applyConsentRulesCommon(jwt, callContext) case (_, false) => Future((Failure(ErrorMessages.ConsentDisabled), Some(callContext))) case (None, _) => Future((Failure(ErrorMessages.ConsentHeaderNotFound), Some(callContext))) } } - def getConsentJwtValueByConsentId(consentId: String): Option[String] = { + def getConsentJwtValueByConsentId(consentId: String): Option[MappedConsent] = { APIUtil.checkIfStringIsUUIDVersion1(consentId) match { case true => // String is a UUID Consents.consentProvider.vend.getConsentByConsentId(consentId) match { - case Full(consent) => Some(consent.jsonWebToken) + case Full(consent) => Some(consent) case _ => None // It's not valid UUID value } case false => None // It's not UUID at all @@ -407,7 +422,7 @@ object Consent { Full(Nil) } } - private def hasBerlinGroupConsentInternal(consentId: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { + private def applyBerlinGroupConsentRulesCommon(consentId: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { implicit val dateFormats = CustomJsonFormats.formats def applyConsentRules(consent: ConsentJWT): Future[(Box[User], Option[CallContext])] = { @@ -464,6 +479,9 @@ object Consent { // 1st we need to find a Consent via the field MappedConsent.consentId Consents.consentProvider.vend.getConsentByConsentId(consentId) match { case Full(storedConsent) => + // Set Consumer into Call Context + val consumer = getCurrentConsumerViaMtls(callContext) + val updatedCallContext = callContext.copy(consumer = consumer) // This function MUST be called only once per call. I.e. it's date dependent val (canBeUsed, currentCounterState) = checkFrequencyPerDay(storedConsent) if(canBeUsed) { @@ -471,28 +489,28 @@ object Consent { case Full(jsonAsString) => try { val consent = net.liftweb.json.parse(jsonAsString).extract[ConsentJWT] - checkConsent(consent, storedConsent.jsonWebToken, callContext) match { // Check is it Consent-JWT expired + checkConsent(consent, storedConsent.jsonWebToken, updatedCallContext) match { // Check is it Consent-JWT expired case (Full(true)) => // OK // Update MappedConsent.usesSoFarTodayCounter field Consents.consentProvider.vend.updateBerlinGroupConsent(consentId, currentCounterState + 1) applyConsentRules(consent) case failure@Failure(_, _, _) => // Handled errors - Future(failure, Some(callContext)) + Future(failure, Some(updatedCallContext)) case _ => // Unexpected errors - Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(callContext)) + Future(Failure(ErrorMessages.ConsentCheckExpiredIssue), Some(updatedCallContext)) } } catch { // Possible exceptions - case e: ParseException => Future(Failure("ParseException: " + e.getMessage), Some(callContext)) - case e: MappingException => Future(Failure("MappingException: " + e.getMessage), Some(callContext)) - case e: Exception => Future(Failure("parsing failed: " + e.getMessage), Some(callContext)) + case e: ParseException => Future(Failure("ParseException: " + e.getMessage), Some(updatedCallContext)) + case e: MappingException => Future(Failure("MappingException: " + e.getMessage), Some(updatedCallContext)) + case e: Exception => Future(Failure("parsing failed: " + e.getMessage), Some(updatedCallContext)) } case failure@Failure(_, _, _) => - Future(failure, Some(callContext)) + Future(failure, Some(updatedCallContext)) case _ => - Future(Failure("Cannot extract data from: " + consentId), Some(callContext)) + Future(Failure("Cannot extract data from: " + consentId), Some(updatedCallContext)) } } else { - Future(Failure(ErrorMessages.TooManyRequests + s" ${RequestHeader.`Consent-ID`}: $consentId"), Some(callContext)) + Future(Failure(ErrorMessages.TooManyRequests + s" ${RequestHeader.`Consent-ID`}: $consentId"), Some(updatedCallContext)) } case failure@Failure(_, _, _) => Future(failure, Some(callContext)) @@ -500,13 +518,10 @@ object Consent { Future(Failure(ErrorMessages.ConsentNotFound + s" ($consentId)"), Some(callContext)) } } - private def hasBerlinGroupConsent(consentId: String, callContext: CallContext): Future[(Box[User], Option[CallContext])] = { - hasBerlinGroupConsentInternal(consentId, callContext) - } def applyBerlinGroupRules(consentId: Option[String], callContext: CallContext): Future[(Box[User], Option[CallContext])] = { val allowed = APIUtil.getPropsAsBoolValue(nameOfProperty="consents.allowed", defaultValue=false) (consentId, allowed) match { - case (Some(consentId), true) => hasBerlinGroupConsent(consentId, callContext) + case (Some(consentId), true) => applyBerlinGroupConsentRulesCommon(consentId, callContext) case (_, false) => Future((Failure(ErrorMessages.ConsentDisabled), Some(callContext))) case (None, _) => Future((Failure(ErrorMessages.ConsentHeaderNotFound), Some(callContext))) } @@ -514,7 +529,7 @@ object Consent { def applyRulesOldStyle(consentId: Option[String], callContext: CallContext): (Box[User], CallContext) = { val allowed = APIUtil.getPropsAsBoolValue(nameOfProperty="consents.allowed", defaultValue=false) (consentId, allowed) match { - case (Some(consentId), true) => hasConsentOldStyle(consentId, callContext) + case (Some(consentId), true) => (applyConsentRulesCommonOldStyle(consentId, callContext), callContext) case (_, false) => (Failure(ErrorMessages.ConsentDisabled), callContext) case (None, _) => (Failure(ErrorMessages.ConsentHeaderNotFound), callContext) } diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 2c6e1a054..21733fe8a 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -198,6 +198,8 @@ object ErrorMessages { s"OBP-20048: If target viewId is system view, the current view.can_revoke_access_to_views does not contains it. Or" + s"if target viewId is custom view, the current view.can_revoke_access_to_custom_views is false." + val SourceViewHasLessPermission = "OBP-20049: Source view contains less permissions than target view." + val UserNotSuperAdmin = "OBP-20050: Current User is not a Super Admin!" val ElasticSearchIndexNotFound = "OBP-20051: Elasticsearch index or indices not found." @@ -399,8 +401,8 @@ object ErrorMessages { val ApiCollectionEndpointNotFound = "OBP-30082: ApiCollectionEndpoint not found." val CreateApiCollectionEndpointError = "OBP-30083: Could not create ApiCollectionEndpoint." val DeleteApiCollectionEndpointError = "OBP-30084: Could not delete ApiCollectionEndpoint." - val ApiCollectionEndpointAlreadyExisting = "OBP-30085: The ApiCollectionEndpoint is already Existing." - val ApiCollectionAlreadyExisting = "OBP-30086: The ApiCollection is already Existing." + val ApiCollectionEndpointAlreadyExists = "OBP-30085: The ApiCollectionEndpoint is already exists." + val ApiCollectionAlreadyExists = "OBP-30086: The ApiCollection is already exists." val DoubleEntryTransactionNotFound = "OBP-30087: Double Entry Transaction not found." @@ -467,7 +469,7 @@ object ErrorMessages { val InsufficientAuthorisationToDeleteBranch = "OBP-30218: Insufficient authorisation to Create Branch. You do not have the role CanCreateBranch." // was OBP-20019 val InsufficientAuthorisationToCreateBank = "OBP-30210: Insufficient authorisation to Create Bank. You do not have the role CanCreateBank." // was OBP-20020 - val InvalidConnector = "OBP-30211: Invalid Connector Version. Please specify a valid value for CONNECTOR." + val InvalidConnector = "OBP-30211: Invalid Connector. Please specify a valid value for CONNECTOR." val EntitlementNotFound = "OBP-30212: EntitlementId not found" val UserDoesNotHaveEntitlement = "OBP-30213: USER_ID does not have the ENTITLEMENT_ID." @@ -492,13 +494,19 @@ object ErrorMessages { val DeleteSystemViewError = "OBP-30251: Could not delete the system view" val SystemViewNotFound = "OBP-30252: System view not found. Please specify a valid value for VIEW_ID" val UpdateSystemViewError = "OBP-30253: Could not update the system view" - val ExistingSystemViewError = "OBP-30254: There is already a view with permalink" + val SystemViewAlreadyExistsError = "OBP-30254: The system view is already exists." val EmptyNameOfSystemViewError = "OBP-30255: You cannot create a View with an empty Name" val DeleteCustomViewError = "OBP-30256: Could not delete the custom view" val CannotFindCustomViewError = "OBP-30257: Could not find the custom view" val SystemViewCannotBePublicError = "OBP-30258: System view cannot be public" val CreateCustomViewError = "OBP-30259: Could not create the custom view" val UpdateCustomViewError = "OBP-30260: Could not update the custom view" + val CreateCounterpartyLimitError = "OBP-30261: Could not create the counterparty limit." + val UpdateCounterpartyLimitError = "OBP-30262: Could not update the counterparty limit." + val GetCounterpartyLimitError = "OBP-30263: Counterparty limit not found. Please specify a valid value for BANK_ID, ACCOUNT_ID, VIEW_ID or COUNTERPARTY_ID." + val CounterpartyLimitAlreadyExists = "OBP-30264: Counterparty limit already exists. Please specify a different value for BANK_ID, ACCOUNT_ID, VIEW_ID or COUNTERPARTY_ID." + val DeleteCounterpartyLimitError = "OBP-30265: Could not delete the counterparty limit." + val CustomViewAlreadyExistsError = "OBP-30266: The custom view is already exists." val TaxResidenceNotFound = "OBP-30300: Tax Residence not found by TAX_RESIDENCE_ID. " val CustomerAddressNotFound = "OBP-30310: Customer's Address not found by CUSTOMER_ADDRESS_ID. " @@ -719,7 +727,7 @@ object ErrorMessages { // MethodRouting Exceptions (OBP-7XXXX) val InvalidBankIdRegex = "OBP-70001: Incorrect regex for bankIdPattern." val MethodRoutingNotFoundByMethodRoutingId = "OBP-70002: MethodRouting not found. Please specify a valid value for method_routing_id." - val ExistingMethodRoutingError = "OBP-70003: Method Routing is already existing." + val MethodRoutingAlreadyExistsError = "OBP-70003: Method Routing is already exists." // Cascade Deletion Exceptions (OBP-8XXXX) val CouldNotDeleteCascade = "OBP-80001: Could not delete cascade." @@ -841,6 +849,11 @@ object ErrorMessages { * validate method: NewStyle.function.checkViewAccessAndReturnView */ def $UserNoPermissionAccessView = UserNoPermissionAccessView + + /** + * validate method: NewStyle.function.getCounterpartyByCounterpartyId + */ + def $CounterpartyNotFoundByCounterpartyId = CounterpartyNotFoundByCounterpartyId def getDuplicatedMessageNumbers = { diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 14a61098a..e931599cf 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -26,7 +26,7 @@ object ExampleValue { val NoDescriptionProvided = "no-description-provided" val NoExampleProvided = "" - val booleanTrue = "true" + val booleanFalse = "false" lazy val bankIdGlossary = glossaryItems.find(_.title == "Bank.bank_id").map(_.textDescription) @@ -105,7 +105,7 @@ object ExampleValue { lazy val dependentsExample = ConnectorField("2", s"the number of dependents") // Dominant form in American English glossaryItems += makeGlossaryItem("Customer.dependents", dependentsExample) - lazy val kycStatusExample = ConnectorField(booleanTrue, s"This is boolean to indicate if the cusomter's KYC has been checked.") + lazy val kycStatusExample = ConnectorField(booleanFalse, s"This is boolean to indicate if the cusomter's KYC has been checked.") glossaryItems += makeGlossaryItem("Customer.kycStatus", kycStatusExample) lazy val urlExample = ConnectorField("http://www.example.com/id-docs/123/image.png", s"The URL ") @@ -179,7 +179,7 @@ object ExampleValue { lazy val otherAccountProviderExample = ConnectorField("", s"")//TODO, not sure what is this field for? glossaryItems += makeGlossaryItem("Transaction.otherAccountProvider", otherAccountProviderExample) - lazy val isBeneficiaryExample = ConnectorField(booleanTrue, s"This is a boolean. True if the originAccount can send money to the Counterparty") + lazy val isBeneficiaryExample = ConnectorField(booleanFalse, s"This is a boolean. True if the originAccount can send money to the Counterparty") glossaryItems += makeGlossaryItem("Counterparty.isBeneficiary", isBeneficiaryExample) lazy val counterpartyNameExample = ConnectorField("John Smith Ltd.", s"The name of a Counterparty. Ideally unique for an Account") @@ -560,7 +560,7 @@ object ExampleValue { lazy val inboundAvroSchemaExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("DynamicMessageDoc.inboundAvroSchema", inboundAvroSchemaExample) - lazy val canSeeImagesExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeImagesExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_images", canSeeImagesExample) lazy val topConsumersExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -572,7 +572,7 @@ object ExampleValue { lazy val maximumResponseTimeExample = ConnectorField("60",NoDescriptionProvided) glossaryItems += makeGlossaryItem("maximum_response_time", maximumResponseTimeExample) - lazy val cancelledExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val cancelledExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("cancelled", cancelledExample) lazy val entitlementRequestsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -620,7 +620,7 @@ object ExampleValue { lazy val canSeeOtherAccountRoutingSchemeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_other_account_routing_scheme", canSeeOtherAccountRoutingSchemeExample) - lazy val canDeleteCorporateLocationExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canDeleteCorporateLocationExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_delete_corporate_location", canDeleteCorporateLocationExample) lazy val fromExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -692,7 +692,7 @@ object ExampleValue { lazy val corporateLocationExample = ConnectorField("10",NoDescriptionProvided) glossaryItems += makeGlossaryItem("corporate_location", corporateLocationExample) - lazy val enabledExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val enabledExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("enabled", enabledExample) lazy val durationExample = ConnectorField("5.123"," This is a decimal number in seconds, eg: 1 for 1 second, 0.001 for 1 ms") @@ -704,7 +704,7 @@ object ExampleValue { lazy val toSepaExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("to_sepa", toSepaExample) - lazy val whichAliasToUseExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) + lazy val whichAliasToUseExample = ConnectorField("public",NoDescriptionProvided) glossaryItems += makeGlossaryItem("which_alias_to_use", whichAliasToUseExample) lazy val canAddImageExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -773,7 +773,7 @@ object ExampleValue { lazy val creatorExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("creator", creatorExample) - lazy val activeExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val activeExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("active", activeExample) lazy val canSeeOtherAccountMetadataExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -965,7 +965,7 @@ object ExampleValue { lazy val canSeeTransactionFinishDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_transaction_finish_date", canSeeTransactionFinishDateExample) - lazy val satisfiedExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val satisfiedExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("satisfied", satisfiedExample) lazy val canSeeOtherAccountIbanExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1172,7 +1172,7 @@ object ExampleValue { lazy val accountOtpExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("account_otp", accountOtpExample) - lazy val hideMetadataIfAliasUsedExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) + lazy val hideMetadataIfAliasUsedExample = ConnectorField(booleanFalse, NoDescriptionProvided) glossaryItems += makeGlossaryItem("hide_metadata_if_alias_used", hideMetadataIfAliasUsedExample) lazy val canSeeBankAccountCurrencyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1576,7 +1576,7 @@ object ExampleValue { lazy val isFirehoseExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("is_firehose", isFirehoseExample) - lazy val okExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val okExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("ok", okExample) lazy val bankRoutingExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1600,7 +1600,7 @@ object ExampleValue { lazy val dependentEndpointsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("dependent_endpoints", dependentEndpointsExample) - lazy val hasDepositCapabilityExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val hasDepositCapabilityExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("ATM.has_deposit_capability", hasDepositCapabilityExample) lazy val toCounterpartyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1624,10 +1624,10 @@ object ExampleValue { lazy val canSeeCommentsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_comments", canSeeCommentsExample) - lazy val canEditOwnerCommentExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canEditOwnerCommentExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_edit_owner_comment", canEditOwnerCommentExample) - lazy val canAddCounterpartyExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canAddCounterpartyExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_add_counterparty", canAddCounterpartyExample) lazy val markdownExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1660,7 +1660,7 @@ object ExampleValue { lazy val narrativeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("narrative", narrativeExample) - lazy val canSeeOtherAccountRoutingAddressExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeOtherAccountRoutingAddressExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_other_account_routing_address", canSeeOtherAccountRoutingAddressExample) lazy val statusesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1696,7 +1696,7 @@ object ExampleValue { lazy val tuesdayExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("tuesday", tuesdayExample) - lazy val canQueryAvailableFundsExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canQueryAvailableFundsExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_query_available_funds", canQueryAvailableFundsExample) lazy val otherAccountSecondaryRoutingSchemeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1782,7 +1782,7 @@ object ExampleValue { lazy val roleExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("role", roleExample) - lazy val requireScopesForListedRolesExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val requireScopesForListedRolesExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("require_scopes_for_listed_roles", requireScopesForListedRolesExample) lazy val branchTypeExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1791,7 +1791,7 @@ object ExampleValue { lazy val fullNameExample = ConnectorField("full name string",NoDescriptionProvided) glossaryItems += makeGlossaryItem("full_name", fullNameExample) - lazy val canCreateDirectDebitExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canCreateDirectDebitExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_create_direct_debit", canCreateDirectDebitExample) lazy val futureDateExample = ConnectorField("20200127",NoDescriptionProvided) @@ -1809,7 +1809,7 @@ object ExampleValue { lazy val documentNumberExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("document_number", documentNumberExample) - lazy val canSeeOtherAccountNationalIdentifierExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeOtherAccountNationalIdentifierExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_other_account_national_identifier", canSeeOtherAccountNationalIdentifierExample) lazy val canSeeTransactionStartDateExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1821,7 +1821,7 @@ object ExampleValue { lazy val cacheExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("cache", cacheExample) - lazy val canSeeBankRoutingAddressExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeBankRoutingAddressExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_bank_routing_address", canSeeBankRoutingAddressExample) lazy val usersExample = ConnectorField("user list", "Please refer to the user object.") @@ -1833,7 +1833,7 @@ object ExampleValue { lazy val ktyExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("kty", ktyExample) - lazy val canBeSeenOnViewsExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canBeSeenOnViewsExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_be_seen_on_views", canBeSeenOnViewsExample) lazy val kidExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1851,7 +1851,7 @@ object ExampleValue { lazy val metadataExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("metadata", metadataExample) - lazy val canSeeTransactionAmountExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeTransactionAmountExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_transaction_amount", canSeeTransactionAmountExample) lazy val methodRoutingIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1875,10 +1875,10 @@ object ExampleValue { lazy val countryCodeExample = ConnectorField("1254",NoDescriptionProvided) glossaryItems += makeGlossaryItem("country_code", countryCodeExample) - lazy val canSeeBankAccountCreditLimitExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeBankAccountCreditLimitExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_bank_account_credit_limit", canSeeBankAccountCreditLimitExample) - lazy val canSeeOtherAccountNumberExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeOtherAccountNumberExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_other_account_number", canSeeOtherAccountNumberExample) lazy val orderExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1896,10 +1896,10 @@ object ExampleValue { lazy val taxResidenceExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("tax_residence", taxResidenceExample) - lazy val isActiveExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val isActiveExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("is_active", isActiveExample) - lazy val canSeeBankAccountBankNameExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeBankAccountBankNameExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_bank_account_bank_name", canSeeBankAccountBankNameExample) lazy val firstNameExample = ConnectorField("Tom","The first name") @@ -1914,7 +1914,7 @@ object ExampleValue { lazy val transactionIdsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("transaction_ids", transactionIdsExample) - lazy val canSeeBankAccountOwnersExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeBankAccountOwnersExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_bank_account_owners", canSeeBankAccountOwnersExample) lazy val actualDateExample = ConnectorField("2020-01-27",NoDescriptionProvided) @@ -1923,10 +1923,10 @@ object ExampleValue { lazy val exampleOutboundMessageExample = ConnectorField("{}","this will the json object") glossaryItems += makeGlossaryItem("example_outbound_message", exampleOutboundMessageExample) - lazy val canDeleteWhereTagExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canDeleteWhereTagExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_delete_where_tag", canDeleteWhereTagExample) - lazy val canSeeUrlExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeUrlExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_url", canSeeUrlExample) lazy val versionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1953,7 +1953,7 @@ object ExampleValue { lazy val implementedInVersionExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("implemented_in_version", implementedInVersionExample) - lazy val canSeeImageUrlExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeImageUrlExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_image_url", canSeeImageUrlExample) lazy val toTransferToPhoneExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -1998,7 +1998,7 @@ object ExampleValue { lazy val eExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("e", eExample) - lazy val canSeeCorporateLocationExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeCorporateLocationExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_corporate_location", canSeeCorporateLocationExample) lazy val userExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -2046,7 +2046,7 @@ object ExampleValue { lazy val requiredfieldinfoExample = ConnectorField("false",NoDescriptionProvided) glossaryItems += makeGlossaryItem("requiredfieldinfo", requiredfieldinfoExample) - lazy val canSeeWhereTagExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeWhereTagExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_where_tag", canSeeWhereTagExample) lazy val bankidExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -2109,10 +2109,10 @@ object ExampleValue { lazy val toSandboxTanExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("to_sandbox_tan", toSandboxTanExample) - lazy val canAddTagExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canAddTagExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_add_tag", canAddTagExample) - lazy val canSeeBankAccountLabelExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeBankAccountLabelExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_bank_account_label", canSeeBankAccountLabelExample) lazy val serviceAvailableExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -2136,7 +2136,7 @@ object ExampleValue { lazy val driveUpExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("drive_up", driveUpExample) - lazy val canAddMoreInfoExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canAddMoreInfoExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_add_more_info", canAddMoreInfoExample) lazy val detailExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -2148,13 +2148,31 @@ object ExampleValue { lazy val transactionRequestTypesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("transaction_request_types", transactionRequestTypesExample) - lazy val canAddImageUrlExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val counterpartyLimitIdExample = ConnectorField("abc9a7e4-6d02-40e3-a129-0b2bf89de9b1","A string that MUST uniquely identify the Counterparty Limit on this OBP instance.") + glossaryItems += makeGlossaryItem("counterparty_limit_id", counterpartyLimitIdExample) + + lazy val maxSingleAmountExample = ConnectorField("1000",NoDescriptionProvided) + glossaryItems += makeGlossaryItem("max_single_amount", maxSingleAmountExample) + + lazy val maxMonthlyAmountExample = ConnectorField("10000",NoDescriptionProvided) + glossaryItems += makeGlossaryItem("max_monthly_amount", maxMonthlyAmountExample) + + lazy val maxNumberOfMonthlyTransactionsExample = ConnectorField("10",NoDescriptionProvided) + glossaryItems += makeGlossaryItem("max_number_of_monthly_transactions", maxNumberOfMonthlyTransactionsExample) + + lazy val maxYearlyAmountExample = ConnectorField("12000",NoDescriptionProvided) + glossaryItems += makeGlossaryItem("max_yearly_amount", maxYearlyAmountExample) + + lazy val maxNumberOfYearlyTransactionsExample = ConnectorField("100",NoDescriptionProvided) + glossaryItems += makeGlossaryItem("max_number_of_yearly_transactions", maxNumberOfYearlyTransactionsExample) + + lazy val canAddImageUrlExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_add_image_url", canAddImageUrlExample) lazy val jwksUrisExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("jwks_uris", jwksUrisExample) - lazy val canSeeOtherAccountSwiftBicExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeOtherAccountSwiftBicExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_other_account_swift_bic", canSeeOtherAccountSwiftBicExample) lazy val staffUserIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -2166,7 +2184,7 @@ object ExampleValue { lazy val validFromExample = ConnectorField("2020-01-27",NoDescriptionProvided) glossaryItems += makeGlossaryItem("valid_from", validFromExample) - lazy val canDeleteImageExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canDeleteImageExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_delete_image", canDeleteImageExample) lazy val toExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) @@ -2178,25 +2196,25 @@ object ExampleValue { lazy val productAttributesExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("product_attributes", productAttributesExample) - lazy val canSeeTransactionDescriptionExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeTransactionDescriptionExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_transaction_description", canSeeTransactionDescriptionExample) lazy val faceImageExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("face_image", faceImageExample) - lazy val canSeeBankAccountNumberExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val canSeeBankAccountNumberExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_see_bank_account_number", canSeeBankAccountNumberExample) lazy val glossaryItemsExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) glossaryItems += makeGlossaryItem("glossary_items", glossaryItemsExample) - lazy val isBankIdExactMatchExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val isBankIdExactMatchExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("is_bank_id_exact_match", isBankIdExactMatchExample) - lazy val isPublicExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val isPublicExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("is_public", isPublicExample) - lazy val isAccessibleExample = ConnectorField(booleanTrue,NoDescriptionProvided) + lazy val isAccessibleExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("ATM.is_accessible", isAccessibleExample) lazy val entitlementIdExample = ConnectorField(NoExampleProvided,NoDescriptionProvided) diff --git a/obp-api/src/main/scala/code/api/util/Glossary.scala b/obp-api/src/main/scala/code/api/util/Glossary.scala index 5a02fcac8..fee0a0b15 100644 --- a/obp-api/src/main/scala/code/api/util/Glossary.scala +++ b/obp-api/src/main/scala/code/api/util/Glossary.scala @@ -3009,10 +3009,10 @@ object Glossary extends MdcLoggable { title = "Account Access", description = s""" - |Account Access is OBP View system. The Account owners can create the view themselves. - |And they can grant/revoke the view to other users to use their view. + |Account Access governs access to Bank Accounts by end Users. It is an intersecting entity between the User and the View Definition. + |A User must have at least one Account Access record record in order to interact with a Bank Account over the OBP API. |""".stripMargin) - + // val allTagNames: Set[String] = ApiTag.allDisplayTagNames // val existingItems: Set[String] = glossaryItems.map(_.title).toSet // allTagNames.diff(existingItems).map(title => glossaryItems += GlossaryItem(title, title)) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index fad386d52..8932ccb83 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -70,6 +70,7 @@ import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo} import code.atmattribute.AtmAttribute import code.bankattribute.BankAttribute import code.connectormethod.{ConnectorMethodProvider, JsonConnectorMethod} +import code.counterpartylimit.{CounterpartyLimit, CounterpartyLimitTrait} import code.crm.CrmEvent import code.crm.CrmEvent.CrmEvent import code.customeraccountlinks.CustomerAccountLinkTrait @@ -372,10 +373,14 @@ object NewStyle extends MdcLoggable{ } } } - - def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]): OBPReturnType[AccountsBalances] = { - Connector.connector.vend.getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) map { i => - (unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2) + def getAccountListThroughView(user : User, viewId: ViewId, callContext: Option[CallContext]): OBPReturnType[List[BankIdAccountId]] = { + val viewIds = List(viewId) + Views.views.vend.getPrivateBankAccountsFuture(user, viewIds) map { i => + if(i.isEmpty) { + (unboxFullOrFail(Empty, callContext, NoViewReadAccountsBerlinGroup , 403), callContext) + } else { + (i, callContext ) + } } } @@ -385,12 +390,6 @@ object NewStyle extends MdcLoggable{ } } - def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[AccountBalances] = { - Connector.connector.vend.getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) map { i => - (unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2) - } - } - def getAccountRouting(bankId: Option[BankId], scheme: String, address: String, callContext: Option[CallContext]) : OBPReturnType[BankAccountRouting] = { Future(Connector.connector.vend.getAccountRouting(bankId: Option[BankId], scheme: String, address : String, callContext: Option[CallContext])) map { i => unboxFullOrFail(i, callContext,s"$AccountRoutingNotFound Current scheme is $scheme, current address is $address, current bankId is $bankId", 404 ) @@ -2927,7 +2926,7 @@ object NewStyle extends MdcLoggable{ } val notExists = if(exists) Empty else Full(true) - (unboxFullOrFail(notExists, callContext, s"$ExistingMethodRoutingError Please modify the following parameters:" + + (unboxFullOrFail(notExists, callContext, s"$MethodRoutingAlreadyExistsError Please modify the following parameters:" + s"is_bank_id_exact_match(${methodRouting.isBankIdExactMatch}), " + s"method_name(${methodRouting.methodName}), " + s"bank_id_pattern(${methodRouting.bankIdPattern.getOrElse("")})" @@ -3464,7 +3463,7 @@ object NewStyle extends MdcLoggable{ def getConnectorByName(connectorName: String): Option[Connector] = { if(supportedConnectorNames.exists(connectorName.startsWith _)) { - Connector.nameToConnector.get(connectorName).map(_()) + Connector.nameToConnector.get(connectorName) } else { None } @@ -4060,6 +4059,67 @@ object NewStyle extends MdcLoggable{ (unboxFullOrFail(i, callContext, s"$DeleteCustomViewError"), callContext) } + def createOrUpdateCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + maxSingleAmount: Int, + maxMonthlyAmount: Int, + maxNumberOfMonthlyTransactions: Int, + maxYearlyAmount: Int, + maxNumberOfYearlyTransactions: Int, + callContext: Option[CallContext] + ): OBPReturnType[CounterpartyLimitTrait] = + Connector.connector.vend.createOrUpdateCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + maxSingleAmount: Int, + maxMonthlyAmount: Int, + maxNumberOfMonthlyTransactions: Int, + maxYearlyAmount: Int, + maxNumberOfYearlyTransactions: Int, + callContext: Option[CallContext] + ) map { + i => (unboxFullOrFail(i._1, callContext, CreateCounterpartyLimitError), i._2) + } + + def getCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + callContext: Option[CallContext] + ): OBPReturnType[CounterpartyLimitTrait] = + Connector.connector.vend.getCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + callContext: Option[CallContext] + ) map { + i => (unboxFullOrFail(i._1, callContext, s"$GetCounterpartyLimitError Current BANK_ID($bankId), " + + s"ACCOUNT_ID($accountId), VIEW_ID($viewId),COUNTERPARTY_ID($counterpartyId)"), i._2) + } + + def deleteCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + callContext: Option[CallContext] + ): OBPReturnType[Boolean] = + Connector.connector.vend.deleteCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + callContext: Option[CallContext] + ) map { + i => (unboxFullOrFail(i._1, callContext, s"$DeleteCounterpartyLimitError"), i._2) + } } } diff --git a/obp-api/src/main/scala/code/api/util/newstyle/BalanceNewStyle.scala b/obp-api/src/main/scala/code/api/util/newstyle/BalanceNewStyle.scala new file mode 100644 index 000000000..ac7028151 --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/newstyle/BalanceNewStyle.scala @@ -0,0 +1,60 @@ +package code.api.util.newstyle + +import code.api.util.APIUtil.{OBPReturnType, unboxFullOrFail} +import code.api.util.ErrorMessages.InvalidConnectorResponseForGetBankAccounts +import code.api.util.{APIUtil, CallContext} +import code.bankconnectors.Connector +import code.views.Views +import com.openbankproject.commons.model.{AccountBalances, AccountsBalances, BankId, BankIdAccountId, User, ViewId} + +import scala.concurrent.Future + +object BalanceNewStyle { + + import com.openbankproject.commons.ExecutionContext.Implicits.global + + def getAccountAccessAtBankThroughView(user: User, + bankId: BankId, + viewId: ViewId, + callContext: Option[CallContext]): OBPReturnType[List[BankIdAccountId]] = { + Future { + val (views, accountAccesses) = Views.views.vend.getAccountAccessAtBankThroughView(user, bankId, viewId) + // Filter views which can read the balance + val canSeeBankAccountBalanceViews = views.filter(_.canSeeBankAccountBalance) + // Filter accounts the user has permission to see balances and remove duplicates + val allowedAccounts = APIUtil.intersectAccountAccessAndView(accountAccesses, canSeeBankAccountBalanceViews) + allowedAccounts + } map { + (_, callContext) + } + } + + def getAccountAccessAtBank(user: User, + bankId: BankId, + callContext: Option[CallContext]): OBPReturnType[List[BankIdAccountId]] = { + Future { + val (views, accountAccesses) = Views.views.vend.privateViewsUserCanAccessAtBank(user, bankId) + // Filter views which can read the balance + val canSeeBankAccountBalanceViews = views.filter(_.canSeeBankAccountBalance) + // Filter accounts the user has permission to see balances and remove duplicates + val allowedAccounts = APIUtil.intersectAccountAccessAndView(accountAccesses, canSeeBankAccountBalanceViews) + allowedAccounts + } map { + (_, callContext) + } + } + + def getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]): OBPReturnType[AccountBalances] = { + Connector.connector.vend.getBankAccountBalances(bankIdAccountId: BankIdAccountId, callContext: Option[CallContext]) map { i => + (unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2) + } + } + + def getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]): OBPReturnType[AccountsBalances] = { + Connector.connector.vend.getBankAccountsBalances(bankIdAccountIds: List[BankIdAccountId], callContext: Option[CallContext]) map { i => + (unboxFullOrFail(i._1, callContext,s"$InvalidConnectorResponseForGetBankAccounts", 400 ), i._2) + } + } + + +} diff --git a/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala index 6ffec8325..968f7f5ed 100644 --- a/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala +++ b/obp-api/src/main/scala/code/api/v1_2_1/APIMethods121.scala @@ -568,7 +568,7 @@ trait APIMethods121 { | The 'alias' field in the JSON can take one of three values: | | * _public_: to use the public alias if there is one specified for the other account. - | * _private_: to use the public alias if there is one specified for the other account. + | * _private_: to use the private alias if there is one specified for the other account. | | * _''(empty string)_: to use no alias; the view shows the real name of the other account. | @@ -645,7 +645,8 @@ trait APIMethods121 { ), List(apiTagAccount, apiTagView, apiTagOldStyle) ) - + + //TODO. remove and replace it with V510. lazy val updateViewForBankAccount: OBPEndpoint = { //updates a view on a bank account case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId diff --git a/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala b/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala index d778fef12..81386e4f4 100644 --- a/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala +++ b/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala @@ -606,15 +606,15 @@ trait APIMethods210 { "Answer Transaction Request Challenge", """In Sandbox mode, any string that can be converted to a positive integer will be accepted as an answer. | - |This endpoint totally depends on createTransactionRequest, it need get the following data from createTransactionRequest response body. + |This endpoint expects the following data as provided in the createTransactionRequest response body: | - |1)`TRANSACTION_REQUEST_TYPE` : is the same as createTransactionRequest request URL . + |1)`TRANSACTION_REQUEST_TYPE` : as per the selected createTransactionRequest type, part of the request URL. | - |2)`TRANSACTION_REQUEST_ID` : is the `id` field in createTransactionRequest response body. + |2)`TRANSACTION_REQUEST_ID` : the value of the `id` field of the createTransactionRequest response body. | - |3) `id` : is `challenge.id` field in createTransactionRequest response body. + |3) `id` : the value of `challenge.id` in the createTransactionRequest response body. | - |4) `answer` : must be `123`. if it is in sandbox mode. If it kafka mode, the answer can be got by phone message or other security ways. + |4) `answer` : Defaults to `123`, if running in sandbox mode. In production mode, the value will be sent via the configured SCA method. | """.stripMargin, challengeAnswerJSON, @@ -1735,4 +1735,4 @@ trait APIMethods210 { } } } -} \ No newline at end of file +} diff --git a/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala b/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala index 2c811b04d..7d5c5a720 100644 --- a/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala +++ b/obp-api/src/main/scala/code/api/v2_2_0/APIMethods220.scala @@ -164,7 +164,7 @@ trait APIMethods220 { | The 'alias' field in the JSON can take one of three values: | | * _public_: to use the public alias if there is one specified for the other account. - | * _private_: to use the public alias if there is one specified for the other account. + | * _private_: to use the private alias if there is one specified for the other account. | | * _''(empty string)_: to use no alias; the view shows the real name of the other account. | @@ -442,7 +442,7 @@ trait APIMethods220 { """.stripMargin, emptyObjectJson, messageDocsJson, - List(UnknownError), + List(InvalidConnector, UnknownError), List(apiTagDocumentation, apiTagApi) ) @@ -452,7 +452,7 @@ trait APIMethods220 { implicit val ec = EndpointContext(Some(cc)) for { connectorObject <- Future(tryo{Connector.getConnectorInstance(connector)}) map { i => - val msg = "$InvalidConnector Current Input is $connector. It should be eg: kafka_vSept2018..." + val msg = s"$InvalidConnector Current Input is $connector. It should be eg: kafka_vSept2018..." unboxFullOrFail(i, cc.callContext, msg) } } yield { diff --git a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala index 992c76ed3..c7a63c729 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/APIMethods300.scala @@ -178,7 +178,7 @@ trait APIMethods300 { | The 'alias' field in the JSON can take one of three values: | | * _public_: to use the public alias if there is one specified for the other account. - | * _private_: to use the public alias if there is one specified for the other account. + | * _private_: to use the private alias if there is one specified for the other account. | | * _''(empty string)_: to use no alias; the view shows the real name of the other account. | @@ -198,6 +198,7 @@ trait APIMethods300 { ), List(apiTagView, apiTagAccount)) + //TODO. remove and replace it with V510. lazy val createViewForBankAccount : OBPEndpoint = { //creates a view on an bank account case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: Nil JsonPost json -> _ => { @@ -500,7 +501,7 @@ trait APIMethods300 { availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u) (coreAccounts, callContext) <- getFilteredCoreAccounts(availablePrivateAccounts, req, callContext) } yield { - (JSONFactory300.createCoreAccountsByCoreAccountsJSON(coreAccounts), HttpCode.`200`(callContext)) + (JSONFactory300.createCoreAccountsByCoreAccountsJSON(coreAccounts, u), HttpCode.`200`(callContext)) } } } @@ -1710,7 +1711,7 @@ trait APIMethods300 { availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) (accounts, callContext) <- getFilteredCoreAccounts(availablePrivateAccounts, req, callContext) } yield { - (JSONFactory300.createCoreAccountsByCoreAccountsJSON(accounts), HttpCode.`200`(callContext)) + (JSONFactory300.createCoreAccountsByCoreAccountsJSON(accounts, u), HttpCode.`200`(callContext)) } } } diff --git a/obp-api/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala b/obp-api/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala index 20cefeceb..3effc4857 100644 --- a/obp-api/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala +++ b/obp-api/src/main/scala/code/api/v3_0_0/JSONFactory3.0.0.scala @@ -846,7 +846,7 @@ object JSONFactory300{ ) ) - def createCoreAccountsByCoreAccountsJSON(coreAccounts : List[CoreAccount]): CoreAccountsJsonV300 = + def createCoreAccountsByCoreAccountsJSON(coreAccounts : List[CoreAccount], user: User): CoreAccountsJsonV300 = CoreAccountsJsonV300(coreAccounts.map(coreAccount => CoreAccountJson( coreAccount.id, coreAccount.label, @@ -854,7 +854,7 @@ object JSONFactory300{ coreAccount.accountType, coreAccount.accountRoutings.map(accountRounting =>AccountRoutingJsonV121(accountRounting.scheme, accountRounting.address)), views = Views.views.vend - .assignedViewsForAccount(BankIdAccountId(BankId(coreAccount.bankId), AccountId(coreAccount.id))).filter(_.isPrivate) + .privateViewsUserCanAccessForAccount(user, BankIdAccountId(BankId(coreAccount.bankId), AccountId(coreAccount.id))).filter(_.isPrivate) .map(mappedView => ViewBasicV300( mappedView.viewId.value, diff --git a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala index 7d2c74424..251bff491 100644 --- a/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala +++ b/obp-api/src/main/scala/code/api/v3_1_0/APIMethods310.scala @@ -17,6 +17,7 @@ import code.api.util.ExampleValue._ import code.api.util.FutureUtil.EndpointContext import code.api.util.NewStyle.HttpCode import code.api.util._ +import code.api.util.newstyle.BalanceNewStyle import code.api.v1_2_1.{JSONFactory, RateLimiting} import code.api.v2_0_0.CreateMeetingJson import code.api.v2_1_0._ @@ -3951,7 +3952,7 @@ trait APIMethods310 { | The 'alias' field in the JSON can take one of two values: | | * _public_: to use the public alias if there is one specified for the other account. - | * _private_: to use the public alias if there is one specified for the other account. + | * _private_: to use the private alias if there is one specified for the other account. | | * _''(empty string)_: to use no alias; the view shows the real name of the other account. | @@ -5935,7 +5936,7 @@ trait APIMethods310 { (Full(u), callContext) <- authenticatedAccess(cc) (_, callContext) <- NewStyle.function.getBank(bankId, callContext) availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) - (accountsBalances, callContext)<- NewStyle.function.getBankAccountsBalances(availablePrivateAccounts, callContext) + (accountsBalances, callContext)<- BalanceNewStyle.getBankAccountsBalances(availablePrivateAccounts, callContext) } yield{ (createBalancesJson(accountsBalances), HttpCode.`200`(callContext)) } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 9ec03fc57..aa2936867 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -29,7 +29,7 @@ import code.api.util._ import code.api.util.migration.Migration import code.api.util.newstyle.AttributeDefinition._ import code.api.util.newstyle.Consumer._ -import code.api.util.newstyle.UserCustomerLinkNewStyle +import code.api.util.newstyle.{BalanceNewStyle, UserCustomerLinkNewStyle} import code.api.util.newstyle.UserCustomerLinkNewStyle.getUserCustomerLinks import code.api.v1_2_1.{JSONFactory, PostTransactionTagJSON} import code.api.v1_4_0.JSONFactory1_4_0 @@ -3346,9 +3346,9 @@ trait APIMethods400 extends MdcLoggable { } staticResourceDocs += ResourceDoc( - getBankAccountsBalances, + getBankAccountsBalancesForCurrentUser, implementedInApiVersion, - nameOf(getBankAccountsBalances), + nameOf(getBankAccountsBalancesForCurrentUser), "GET", "/banks/BANK_ID/balances", "Get Accounts Balances", @@ -3359,23 +3359,23 @@ trait APIMethods400 extends MdcLoggable { apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil ) - lazy val getBankAccountsBalances : OBPEndpoint = { + lazy val getBankAccountsBalancesForCurrentUser : OBPEndpoint = { case "banks" :: BankId(bankId) :: "balances" :: Nil JsonGet _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { (Full(u), callContext) <- SS.user - availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) - (accountsBalances, callContext)<- NewStyle.function.getBankAccountsBalances(availablePrivateAccounts, callContext) - } yield{ + (allowedAccounts, callContext) <- BalanceNewStyle.getAccountAccessAtBank(u, bankId, callContext) + (accountsBalances, callContext)<- BalanceNewStyle.getBankAccountsBalances(allowedAccounts, callContext) + } yield { (createBalancesJson(accountsBalances), HttpCode.`200`(callContext)) } } } staticResourceDocs += ResourceDoc( - getBankAccountBalances, + getBankAccountBalancesForCurrentUser, implementedInApiVersion, - nameOf(getBankAccountBalances), + nameOf(getBankAccountBalancesForCurrentUser), "GET", "/banks/BANK_ID/accounts/ACCOUNT_ID/balances", "Get Account Balances", @@ -3386,15 +3386,18 @@ trait APIMethods400 extends MdcLoggable { apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil ) - lazy val getBankAccountBalances : OBPEndpoint = { + lazy val getBankAccountBalancesForCurrentUser : OBPEndpoint = { case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonGet _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { (Full(u), callContext) <- SS.user - availablePrivateAccounts <- Views.views.vend.getPrivateBankAccountsFuture(u, bankId) - bankIdAcconutId <- NewStyle.function.tryons(s"$CannotFindAccountAccess AccountId(${accountId.value})", 400, cc.callContext) {availablePrivateAccounts.find(_.accountId==accountId).get} - (accountBalances, callContext)<- NewStyle.function.getBankAccountBalances(bankIdAcconutId, callContext) - } yield{ + (allowedAccounts, callContext) <- BalanceNewStyle.getAccountAccessAtBank(u, bankId, callContext) + msg = s"$CannotFindAccountAccess AccountId(${accountId.value})" + bankIdAccountId <- NewStyle.function.tryons(msg, 400, cc.callContext) { + allowedAccounts.find(_.accountId==accountId).get + } + (accountBalances, callContext)<- BalanceNewStyle.getBankAccountBalances(bankIdAccountId, callContext) + } yield { (createAccountBalancesJson(accountBalances), HttpCode.`200`(callContext)) } } @@ -9013,7 +9016,7 @@ trait APIMethods400 extends MdcLoggable { json.extract[PostApiCollectionJson400] } apiCollection <- Future{MappedApiCollectionsProvider.getApiCollectionByUserIdAndCollectionName(cc.userId, postJson.api_collection_name)} - _ <- Helper.booleanToFuture(failMsg = s"$ApiCollectionAlreadyExisting Current api_collection_name(${postJson.api_collection_name}) is already existing for the log in user.", cc=cc.callContext) { + _ <- Helper.booleanToFuture(failMsg = s"$ApiCollectionAlreadyExists Current api_collection_name(${postJson.api_collection_name}) is already existing for the log in user.", cc=cc.callContext) { apiCollection.isEmpty } (apiCollection, callContext) <- NewStyle.function.createApiCollection( @@ -9296,7 +9299,7 @@ trait APIMethods400 extends MdcLoggable { } (apiCollection, callContext) <- NewStyle.function.getApiCollectionByUserIdAndCollectionName(cc.userId, apiCollectionName, Some(cc)) apiCollectionEndpoint <- Future{MappedApiCollectionEndpointsProvider.getApiCollectionEndpointByApiCollectionIdAndOperationId(apiCollection.apiCollectionId, postJson.operation_id)} - _ <- Helper.booleanToFuture(failMsg = s"$ApiCollectionEndpointAlreadyExisting Current OPERATION_ID(${postJson.operation_id}) is already in API_COLLECTION_NAME($apiCollectionName) ", cc=callContext) { + _ <- Helper.booleanToFuture(failMsg = s"$ApiCollectionEndpointAlreadyExists Current OPERATION_ID(${postJson.operation_id}) is already in API_COLLECTION_NAME($apiCollectionName) ", cc=callContext) { apiCollectionEndpoint.isEmpty } (apiCollectionEndpoint, callContext) <- NewStyle.function.createApiCollectionEndpoint( @@ -9345,7 +9348,7 @@ trait APIMethods400 extends MdcLoggable { } (apiCollection, callContext) <- NewStyle.function.getApiCollectionById(apiCollectionId, Some(cc)) apiCollectionEndpoint <- Future{MappedApiCollectionEndpointsProvider.getApiCollectionEndpointByApiCollectionIdAndOperationId(apiCollection.apiCollectionId, postJson.operation_id)} - _ <- Helper.booleanToFuture(failMsg = s"$ApiCollectionEndpointAlreadyExisting Current OPERATION_ID(${postJson.operation_id}) is already in API_COLLECTION_ID($apiCollectionId) ", cc=callContext) { + _ <- Helper.booleanToFuture(failMsg = s"$ApiCollectionEndpointAlreadyExists Current OPERATION_ID(${postJson.operation_id}) is already in API_COLLECTION_ID($apiCollectionId) ", cc=callContext) { apiCollectionEndpoint.isEmpty } (apiCollectionEndpoint, callContext) <- NewStyle.function.createApiCollectionEndpoint( diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index 221925173..7f55f5010 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -45,6 +45,7 @@ import java.util.concurrent.ThreadLocalRandom import code.accountattribute.AccountAttributeX import code.api.Constant.SYSTEM_OWNER_VIEW_ID import code.api.util.FutureUtil.EndpointContext +import code.consumer.Consumers import code.util.Helper.booleanToFuture import code.views.system.{AccountAccess, ViewDefinition} @@ -991,7 +992,15 @@ trait APIMethods500 { case Props.RunModes.Test => Consent.challengeAnswerAtTestEnvironment case _ => SecureRandomUtil.numeric() } - createdConsent <- Future(Consents.consentProvider.vend.createObpConsent(user, challengeAnswer, Some(consentRequestId))) map { + consumer = Consumers.consumers.vend.getConsumerByConsumerId(calculatedConsumerId.getOrElse("None")) + createdConsent <- Future( + Consents.consentProvider.vend.createObpConsent( + user, + challengeAnswer, + Some(consentRequestId), + consumer + ) + ) map { i => connectorEmptyResponse(i, callContext) } @@ -1852,18 +1861,19 @@ trait APIMethods500 { s"""Create a system view | | ${authenticationRequiredMessage(true)} and the user needs to have access to the $canCreateSystemView entitlement. - | The 'alias' field in the JSON can take one of two values: + | + | The 'allowed_actions' field is a list containing the names of the actions allowed through this view. + | All the actions contained in the list will be set to `true` on the view creation, the rest will be set to `false`. + | + | The 'alias' field in the JSON can take one of three values: | | * _public_: to use the public alias if there is one specified for the other account. - | * _private_: to use the public alias if there is one specified for the other account. - | + | * _private_: to use the private alias if there is one specified for the other account. | * _''(empty string)_: to use no alias; the view shows the real name of the other account. | | The 'hide_metadata_if_alias_used' field in the JSON can take boolean values. If it is set to `true` and there is an alias on the other account then the other accounts' metadata (like more_info, url, image_url, open_corporates_url, etc.) will be hidden. Otherwise the metadata will be shown. | - | The 'allowed_actions' field is a list containing the name of the actions allowed on this view, all the actions contained will be set to `true` on the view creation, the rest will be set to `false`. - | - | Please note that system views cannot be public. In case you try to set it you will get the error $SystemViewCannotBePublicError + | System views cannot be public. In case you try to set it you will get the error $SystemViewCannotBePublicError | """, createSystemViewJsonV500, viewJsonV500, diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 97acaf104..7d1c52012 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -1,6 +1,7 @@ package code.api.v5_1_0 +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.{Constant, UserNotFound} import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ import code.api.util.APIUtil._ @@ -12,13 +13,17 @@ import code.api.util.JwtUtil.{getSignedPayloadAsJson, verifyJwt} import code.api.util.NewStyle.HttpCode import code.api.util.X509.{getCommonName, getEmailAddress, getOrganization} import code.api.util._ +import code.api.util.newstyle.BalanceNewStyle import code.api.util.newstyle.Consumer.createConsumerNewStyle import code.api.util.newstyle.RegulatedEntityNewStyle.{createRegulatedEntityNewStyle, deleteRegulatedEntityNewStyle, getRegulatedEntitiesNewStyle, getRegulatedEntityByEntityIdNewStyle} +import code.api.v1_2_1.CreateViewJsonV121 import code.api.v2_1_0.{ConsumerRedirectUrlJSON, JSONFactory210} +import code.api.v2_2_0.JSONFactory220 import code.api.v3_0_0.JSONFactory300 import code.api.v3_0_0.JSONFactory300.createAggregateMetricJson import code.api.v3_1_0.ConsentJsonV310 import code.api.v3_1_0.JSONFactory310.createBadLoginStatusJson +import code.api.v4_0_0.JSONFactory400.{createAccountBalancesJson, createBalancesJson, createNewCoreBankAccountJson} import code.api.v4_0_0.{JSONFactory400, PostAccountAccessJsonV400, PostApiCollectionJson400, RevokedJsonV400} import code.api.v5_1_0.JSONFactory510.{createRegulatedEntitiesJson, createRegulatedEntityJson} import code.atmattribute.AtmAttribute @@ -26,14 +31,14 @@ import code.bankconnectors.Connector import code.consent.Consents import code.loginattempts.LoginAttempt import code.metrics.APIMetrics -import code.model.AppType +import code.model.{AppType, BankAccountX} import code.model.dataAccess.MappedBankAccount import code.regulatedentities.MappedRegulatedEntityProvider import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _} import code.userlocks.UserLocksProvider import code.users.Users import code.util.Helper -import code.util.Helper.ObpS +import code.util.Helper.{ObpS, booleanToBox} import code.views.Views import code.views.system.{AccountAccess, ViewDefinition} import com.github.dwickern.macros.NameOf.nameOf @@ -43,9 +48,9 @@ import com.openbankproject.commons.model._ import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} import net.liftweb.common.Full import net.liftweb.http.rest.RestHelper -import net.liftweb.json.{compactRender, parse, prettyRender} +import net.liftweb.json.{Extraction, compactRender, parse, prettyRender} import net.liftweb.mapper.By -import net.liftweb.util.Helpers +import net.liftweb.util.{Helpers, StringHelpers} import net.liftweb.util.Helpers.tryo import scala.collection.immutable.{List, Nil} @@ -992,9 +997,9 @@ trait APIMethods510 { cc => implicit val ec = EndpointContext(Some(cc)) for { consent <- Future { Consents.consentProvider.vend.getConsentByConsentId(consentId)} map { - unboxFullOrFail(_, cc.callContext, ConsentNotFound) + unboxFullOrFail(_, cc.callContext, ConsentNotFound, 404) } - _ <- Helper.booleanToFuture(failMsg = ConsentNotFound, cc = cc.callContext) { + _ <- Helper.booleanToFuture(failMsg = ConsentNotFound, failCode = 404, cc = cc.callContext) { consent.mUserId == cc.userId } } yield { @@ -1093,7 +1098,7 @@ trait APIMethods510 { (Full(user), callContext) <- authenticatedAccess(cc) consentId = getConsentIdRequestHeaderValue(cc.requestHeaders).getOrElse("") _ <- Future(Consents.consentProvider.vend.getConsentByConsentId(consentId)) map { - unboxFullOrFail(_, callContext, ConsentNotFound) + unboxFullOrFail(_, callContext, ConsentNotFound, 404) } consent <- Future(Consents.consentProvider.vend.revoke(consentId)) map { i => connectorEmptyResponse(i, callContext) @@ -2155,11 +2160,570 @@ trait APIMethods510 { + staticResourceDocs += ResourceDoc( + getCoreAccountByIdThroughView, + implementedInApiVersion, + nameOf(getCoreAccountByIdThroughView), + "GET", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID", + "Get Account by Id (Core) through the VIEW_ID", + s"""Information returned about the account through VIEW_ID : + |""".stripMargin, + EmptyBody, + moderatedCoreAccountJsonV400, + List($UserNotLoggedIn, $BankAccountNotFound,UnknownError), + apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil + ) + lazy val getCoreAccountByIdThroughView : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) :: Nil JsonGet req => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (user @Full(u), account, callContext) <- SS.userAccount + bankIdAccountId = BankIdAccountId(account.bankId, account.accountId) + view <- NewStyle.function.checkViewAccessAndReturnView(viewId , bankIdAccountId, user, callContext) + moderatedAccount <- NewStyle.function.moderatedBankAccountCore(account, view, user, callContext) + } yield { + val availableViews: List[View] = Views.views.vend.privateViewsUserCanAccessForAccount(u, BankIdAccountId(account.bankId, account.accountId)) + (createNewCoreBankAccountJson(moderatedAccount, availableViews), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getBankAccountBalances, + implementedInApiVersion, + nameOf(getBankAccountBalances), + "GET", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/balances", + "Get Account Balances by BANK_ID and ACCOUNT_ID through the VIEW_ID", + """Get the Balances for the Account specified by BANK_ID and ACCOUNT_ID through the VIEW_ID.""", + EmptyBody, + accountBalanceV400, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + UserNoPermissionAccessView, + UnknownError + ), + apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil + ) + + lazy val getBankAccountBalances : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) :: "balances" :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + bankIdAccountId = BankIdAccountId(bankId, accountId) + view <- NewStyle.function.checkViewAccessAndReturnView(viewId, bankIdAccountId, Full(u), callContext) + // Note we do one explicit check here rather than use moderated account because this provide an explicit message + failMsg = ViewDoesNotPermitAccess + " You need the permission canSeeBankAccountBalance." + _ <- Helper.booleanToFuture(failMsg, 403, cc = callContext) { + view.canSeeBankAccountBalance + } + (accountBalances, callContext) <- BalanceNewStyle.getBankAccountBalances(bankIdAccountId, callContext) + } yield { + (createAccountBalancesJson(accountBalances), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getBankAccountsBalances, + implementedInApiVersion, + nameOf(getBankAccountsBalances), + "GET", + "/banks/BANK_ID/balances", + "Get Account Balances by BANK_ID", + """Get the Balances for the Account specified by BANK_ID.""", + EmptyBody, + accountBalancesV400Json, + List( + $UserNotLoggedIn, + $BankNotFound, + UnknownError + ), + apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil + ) + + lazy val getBankAccountsBalances : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "balances" :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (allowedAccounts, callContext) <- BalanceNewStyle.getAccountAccessAtBank(u, bankId, callContext) + (accountsBalances, callContext) <- BalanceNewStyle.getBankAccountsBalances(allowedAccounts, callContext) + } yield { + (createBalancesJson(accountsBalances), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getBankAccountsBalancesThroughView, + implementedInApiVersion, + nameOf(getBankAccountsBalancesThroughView), + "GET", + "/banks/BANK_ID/views/VIEW_ID/balances", + "Get Account Balances by BANK_ID", + """Get the Balances for the Account specified by BANK_ID.""", + EmptyBody, + accountBalancesV400Json, + List( + $UserNotLoggedIn, + $BankNotFound, + UnknownError + ), + apiTagAccount :: apiTagPSD2AIS :: apiTagPsd2 :: Nil + ) + + lazy val getBankAccountsBalancesThroughView : OBPEndpoint = { + case "banks" :: BankId(bankId) :: "views" :: ViewId(viewId) :: "balances" :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (allowedAccounts, callContext) <- BalanceNewStyle.getAccountAccessAtBankThroughView(u, bankId, viewId, callContext) + (accountsBalances, callContext) <- BalanceNewStyle.getBankAccountsBalances(allowedAccounts, callContext) + } yield { + (createBalancesJson(accountsBalances), HttpCode.`200`(callContext)) + } + } + } + lazy val counterPartyLimitIntro: String = + """Counter Party Limits can be used to restrict the Transaction Request amounts and frequencies (per month and year) that can be made to a Counterparty (Beneficiary). + | + |In order to implement VRP (Variable Recurring Payments) perform the following steps: + |1) Create a Custom View named e.g. VRP1. + |2) Place a Beneficiary Counterparty on that view. + |3) Add Counterparty Limits for that Counterparty. + |4) Generate a Consent containing the bank, account and view (e.g. VRP1) + |5) Let the App use the consent to trigger Transaction Requests. + |""".stripMargin + + staticResourceDocs += ResourceDoc( + createCounterpartyLimit, + implementedInApiVersion, + nameOf(createCounterpartyLimit), + "POST", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/counterparties/COUNTERPARTY_ID/limits", + "Create Counterparty Limit", + s"""Create Counterparty Limit. + | + |$counterPartyLimitIntro + | + |""".stripMargin, + postCounterpartyLimitV510, + counterpartyLimitV510, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + $CounterpartyNotFoundByCounterpartyId, + InvalidJsonFormat, + UnknownError + ), + List(apiTagCounterpartyLimits), + ) + lazy val createCounterpartyLimit: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) ::"counterparties" :: CounterpartyId(counterpartyId) ::"limits" :: Nil JsonPost json -> _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + postCounterpartyLimitV510 <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[AtmJsonV510]}", 400, cc.callContext) { + json.extract[PostCounterpartyLimitV510] + } + (counterpartyLimitBox, callContext) <- Connector.connector.vend.getCounterpartyLimit( + bankId.value, + accountId.value, + viewId.value, + counterpartyId.value, + cc.callContext + ) + failMsg = s"$CounterpartyLimitAlreadyExists Current BANK_ID($bankId), ACCOUNT_ID($accountId), VIEW_ID($viewId),COUNTERPARTY_ID($counterpartyId)" + _ <- Helper.booleanToFuture(failMsg, cc = callContext) { + counterpartyLimitBox.isEmpty + } + (counterpartyLimit,callContext) <- NewStyle.function.createOrUpdateCounterpartyLimit( + bankId.value, + accountId.value, + viewId.value, + counterpartyId.value, + postCounterpartyLimitV510.max_single_amount, + postCounterpartyLimitV510.max_monthly_amount, + postCounterpartyLimitV510.max_number_of_monthly_transactions, + postCounterpartyLimitV510.max_yearly_amount, + postCounterpartyLimitV510.max_number_of_yearly_transactions, + cc.callContext + ) + } yield { + + (counterpartyLimit.toJValue, HttpCode.`201`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + updateCounterpartyLimit, + implementedInApiVersion, + nameOf(updateCounterpartyLimit), + "PUT", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/counterparties/COUNTERPARTY_ID/limits", + "Update Counterparty Limit", + s"""Update Counterparty Limit.""", + postCounterpartyLimitV510, + counterpartyLimitV510, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + $CounterpartyNotFoundByCounterpartyId, + InvalidJsonFormat, + UnknownError + ), + List(apiTagCounterpartyLimits), + ) + lazy val updateCounterpartyLimit: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) ::"counterparties" :: CounterpartyId(counterpartyId) ::"limits" :: Nil JsonPut json -> _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + postCounterpartyLimitV510 <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[AtmJsonV510]}", 400, cc.callContext) { + json.extract[PostCounterpartyLimitV510] + } + (counterpartyLimit,callContext) <- NewStyle.function.createOrUpdateCounterpartyLimit( + bankId.value, + accountId.value, + viewId.value, + counterpartyId.value, + postCounterpartyLimitV510.max_single_amount, + postCounterpartyLimitV510.max_monthly_amount, + postCounterpartyLimitV510.max_number_of_monthly_transactions, + postCounterpartyLimitV510.max_yearly_amount, + postCounterpartyLimitV510.max_number_of_yearly_transactions, + cc.callContext + ) + } yield { + (counterpartyLimit.toJValue, HttpCode.`200`(cc.callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getCounterpartyLimit, + implementedInApiVersion, + nameOf(getCounterpartyLimit), + "GET", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/counterparties/COUNTERPARTY_ID/limits", + "Get Counterparty Limit", + s"""Get Counterparty Limit.""", + EmptyBody, + counterpartyLimitV510, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + $CounterpartyNotFoundByCounterpartyId, + InvalidJsonFormat, + UnknownError + ), + List(apiTagCounterpartyLimits), + ) + lazy val getCounterpartyLimit: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) ::"counterparties" :: CounterpartyId(counterpartyId) ::"limits" :: Nil JsonGet _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (counterpartyLimit, callContext) <- NewStyle.function.getCounterpartyLimit( + bankId.value, + accountId.value, + viewId.value, + counterpartyId.value, + cc.callContext + ) + } yield { + (counterpartyLimit.toJValue, HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + deleteCounterpartyLimit, + implementedInApiVersion, + nameOf(deleteCounterpartyLimit), + "DELETE", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/counterparties/COUNTERPARTY_ID/limits", + "Delete Counterparty Limit", + s"""Delete Counterparty Limit.""", + EmptyBody, + EmptyBody, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + $CounterpartyNotFoundByCounterpartyId, + InvalidJsonFormat, + UnknownError + ), + List(apiTagCounterpartyLimits), + ) + lazy val deleteCounterpartyLimit: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) ::"counterparties" :: CounterpartyId(counterpartyId) ::"limits" :: Nil JsonDelete _ => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (counterpartyLimit, callContext)<- NewStyle.function.deleteCounterpartyLimit( + bankId.value, + accountId.value, + viewId.value, + counterpartyId.value, + cc.callContext + ) + } yield { + (Full(counterpartyLimit), HttpCode.`204`(cc.callContext)) + } + } + } + + resourceDocs += ResourceDoc( + createCustomView, + implementedInApiVersion, + nameOf(createCustomView), + "POST", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/target-views", + "Create Custom View", + s"""Create a custom view on bank account + | + | ${authenticationRequiredMessage(true)} and the user needs to have access to the owner view. + | The 'alias' field in the JSON can take one of three values: + | + | * _public_: to use the public alias if there is one specified for the other account. + | * _private_: to use the private alias if there is one specified for the other account. + | + | * _''(empty string)_: to use no alias; the view shows the real name of the other account. + | + | The 'hide_metadata_if_alias_used' field in the JSON can take boolean values. If it is set to `true` and there is an alias on the other account then the other accounts' metadata (like more_info, url, image_url, open_corporates_url, etc.) will be hidden. Otherwise the metadata will be shown. + | + | The 'allowed_actions' field is a list containing the name of the actions allowed on this view, all the actions contained will be set to `true` on the view creation, the rest will be set to `false`. + | + | You MUST use a leading _ (underscore) in the view name because other view names are reserved for OBP [system views](/index#group-View-System). + | """, + createCustomViewJson, + customViewJsonV510, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + InvalidJsonFormat, + UnknownError + ), + List(apiTagView, apiTagAccount) + ) + lazy val createCustomView: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) ::"target-views" :: Nil JsonPost json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (_, _, _, view, callContext) <- SS.userBankAccountView + createCustomViewJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[CreateViewJson]}", 400, cc.callContext) { + json.extract[CreateCustomViewJson] + } + //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner + _ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat + s"Current view_name (${createCustomViewJson.name})", cc = callContext) { + isValidCustomViewName(createCustomViewJson.name) + } + + permissionsFromSource = APIUtil.getViewPermissions(view.asInstanceOf[ViewDefinition]) + permissionsFromTarget = createCustomViewJson.allowed_permissions + + _ <- Helper.booleanToFuture(failMsg = SourceViewHasLessPermission + s"Current source viewId($viewId) permissions ($permissionsFromSource), target viewName${createCustomViewJson.name} permissions ($permissionsFromTarget)", cc = callContext) { + permissionsFromTarget.toSet.subsetOf(permissionsFromSource) + } + + failMsg = s"${ErrorMessages.ViewDoesNotPermitAccess} You need the `${StringHelpers.snakify(nameOf(view.canCreateCustomView))}` permission on VIEW_ID(${viewId.value})" + + _ <- Helper.booleanToFuture(failMsg, cc = callContext) { + view.canCreateCustomView + } + (view, callContext) <- NewStyle.function.createCustomView(BankIdAccountId(bankId, accountId), createCustomViewJson.toCreateViewJson, callContext) + } yield { + (JSONFactory510.createViewJson(view), HttpCode.`201`(callContext)) + } + } + } + + resourceDocs += ResourceDoc( + updateCustomView, + implementedInApiVersion, + nameOf(updateCustomView), + "PUT", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/target-views/TARGET_VIEW_ID", + "Update Custom View", + s"""Update an existing custom view on a bank account + | + |${authenticationRequiredMessage(true)} and the user needs to have access to the owner view. + | + |The json sent is the same as during view creation (above), with one difference: the 'name' field + |of a view is not editable (it is only set when a view is created)""", + updateCustomViewJson, + customViewJsonV510, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + InvalidJsonFormat, + UnknownError + ), + List(apiTagView, apiTagAccount) + ) + lazy val updateCustomView: OBPEndpoint = { + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) :: "target-views" :: ViewId(targetViewId) :: Nil JsonPut json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (_, _, _, view, callContext) <- SS.userBankAccountView + targetCreateCustomViewJson <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[UpdateCustomViewJson]}", 400, cc.callContext) { + json.extract[UpdateCustomViewJson] + } + //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner + _ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat + s"Current TARGET_VIEW_ID (${targetViewId})", cc = callContext) { + isValidCustomViewId(targetViewId.value) + } + permissionsFromSource = APIUtil.getViewPermissions(view.asInstanceOf[ViewDefinition]) + permissionsFromTarget = targetCreateCustomViewJson.allowed_permissions + + _ <- Helper.booleanToFuture(failMsg = SourceViewHasLessPermission + s"Current source view permissions ($permissionsFromSource), target view permissions ($permissionsFromTarget)", cc = callContext) { + permissionsFromTarget.toSet.subsetOf(permissionsFromSource) + } + + failmsg = s"${ErrorMessages.ViewDoesNotPermitAccess} You need the `${StringHelpers.snakify(nameOf(view.canUpdateCustomView))}` permission on VIEW_ID(${viewId.value})" + + _ <- Helper.booleanToFuture(failmsg, cc = callContext) { + view.canCreateCustomView + } + + (view, callContext) <- NewStyle.function.updateCustomView(BankIdAccountId(bankId, accountId), targetViewId, targetCreateCustomViewJson.toUpdateViewJson, callContext) + } yield { + (JSONFactory510.createViewJson(view), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getCustomView, + implementedInApiVersion, + nameOf(getCustomView), + "GET", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/target-views/TARGET_VIEW_ID", + "Get Custom View", + s"""#Views + | + | + |Views in Open Bank Project provide a mechanism for fine grained access control and delegation to Accounts and Transactions. Account holders use the 'owner' view by default. Delegated access is made through other views for example 'accountants', 'share-holders' or 'tagging-application'. Views can be created via the API and each view has a list of entitlements. + | + |Views on accounts and transactions filter the underlying data to redact certain fields for certain users. For instance the balance on an account may be hidden from the public. The way to know what is possible on a view is determined in the following JSON. + | + |**Data:** When a view moderates a set of data, some fields my contain the value `null` rather than the original value. This indicates either that the user is not allowed to see the original data or the field is empty. + | + |There is currently one exception to this rule; the 'holder' field in the JSON contains always a value which is either an alias or the real name - indicated by the 'is_alias' field. + | + |**Action:** When a user performs an action like trying to post a comment (with POST API call), if he is not allowed, the body response will contain an error message. + | + |**Metadata:** + |Transaction metadata (like images, tags, comments, etc.) will appears *ONLY* on the view where they have been created e.g. comments posted to the public view only appear on the public view. + | + |The other account metadata fields (like image_URL, more_info, etc.) are unique through all the views. Example, if a user edits the 'more_info' field in the 'team' view, then the view 'authorities' will show the new value (if it is allowed to do it). + | + |# All + |*Optional* + | + |Returns the list of the views created for account ACCOUNT_ID at BANK_ID. + | + |${authenticationRequiredMessage(true)} and the user needs to have access to the owner view.""", + EmptyBody, + customViewJsonV510, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + UnknownError + ), + List(apiTagView, apiTagAccount) + ) + lazy val getCustomView: OBPEndpoint = { + //get the available views on an bank account + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) :: "target-views" :: ViewId(targetViewId):: Nil JsonGet req => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (_, _, _, view, callContext) <- SS.userBankAccountView + //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner + _ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat + s"Current TARGET_VIEW_ID (${targetViewId.value})", cc = callContext) { + isValidCustomViewId(targetViewId.value) + } + failmsg = s"${ErrorMessages.ViewDoesNotPermitAccess} You need the `${StringHelpers.snakify(nameOf(view.canSeeAvailableViewsForBankAccount))}`permission on any your views. Current VIEW_ID (${viewId.value})" + _ <- Helper.booleanToFuture(failmsg, cc = callContext) { + view.canSeeAvailableViewsForBankAccount + } + targetView <- NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) + } yield { + (JSONFactory510.createViewJson(targetView), HttpCode.`200`(callContext)) + } + } + } + + + staticResourceDocs += ResourceDoc( + deleteCustomView, + implementedInApiVersion, + nameOf(deleteCustomView), + "DELETE", + "/banks/BANK_ID/accounts/ACCOUNT_ID/views/VIEW_ID/target-views/TARGET_VIEW_ID", + "Delete Custom View", + "Deletes the custom view specified by VIEW_ID on the bank account specified by ACCOUNT_ID at bank BANK_ID", + EmptyBody, + EmptyBody, + List( + $UserNotLoggedIn, + $BankNotFound, + $BankAccountNotFound, + $UserNoPermissionAccessView, + UnknownError + ), + List(apiTagView, apiTagAccount) + ) + + lazy val deleteCustomView: OBPEndpoint = { + //deletes a view on an bank account + case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId ) :: "views" :: ViewId(viewId) :: "target-views" :: ViewId(targetViewId) :: Nil JsonDelete req => { + cc => implicit val ec = EndpointContext(Some(cc)) + for { + (_, _, _, view, callContext) <- SS.userBankAccountView + //customer views are started ith `_`,eg _life, _work, and System views startWith letter, eg: owner + _ <- Helper.booleanToFuture(failMsg = InvalidCustomViewFormat + s"Current TARGET_VIEW_ID (${targetViewId.value})", cc = callContext) { + isValidCustomViewId(targetViewId.value) + } + failMsg = s"${ErrorMessages.ViewDoesNotPermitAccess} You need the `${StringHelpers.snakify(nameOf(view.canDeleteCustomView))}` permission on any your views.Current VIEW_ID (${viewId.value})" + _ <- Helper.booleanToFuture(failMsg, cc = callContext) { + view.canDeleteCustomView + } + _ <- NewStyle.function.customView(targetViewId, BankIdAccountId(bankId, accountId), callContext) + deleted <- NewStyle.function.removeCustomView(targetViewId, BankIdAccountId(bankId, accountId), callContext) + } yield { + (Full(deleted), HttpCode.`204`(callContext)) + } + } + } + } } + + object APIMethods510 extends RestHelper with APIMethods510 { lazy val newStyleEndpoints: List[(String, String)] = Implementations5_1_0.resourceDocs.map { rd => (rd.partialFunctionName, rd.implementedInApiVersion.toString()) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index fe92e53e7..a7c047658 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -28,17 +28,17 @@ package code.api.v5_1_0 import code.api.Constant import code.api.util.{APIUtil, ConsentJWT, CustomJsonFormats, JwtUtil, Role} -import code.api.util.APIUtil.gitCommit +import code.api.util.APIUtil.{gitCommit, stringOrNull} import code.api.v1_4_0.JSONFactory1_4_0.{LocationJsonV140, MetaJsonV140, transformToLocationFromV140, transformToMetaFromV140} import code.api.v2_1_0.ResourceUserJSON import code.api.v3_0_0.JSONFactory300.{createLocationJson, createMetaJson, transformToAddressFromV300} -import code.api.v3_0_0.{AccountIdJson, AccountsIdsJsonV300, AddressJsonV300, OpeningTimesV300} +import code.api.v3_0_0.{AccountIdJson, AccountsIdsJsonV300, AddressJsonV300, OpeningTimesV300, ViewJsonV300} import code.api.v4_0_0.{EnergySource400, HostedAt400, HostedBy400, PostViewJsonV400} import code.atmattribute.AtmAttribute import code.atms.Atms.Atm import code.users.{UserAttribute, Users} import code.views.system.{AccountAccess, ViewDefinition} -import com.openbankproject.commons.model.{Address, AtmId, AtmT, BankId, BankIdAccountId, Customer, Location, Meta, RegulatedEntityTrait} +import com.openbankproject.commons.model.{Address, AtmId, AtmT, BankId, BankIdAccountId, CreateViewJson, Customer, Location, Meta, RegulatedEntityTrait, UpdateViewJSON, View} import com.openbankproject.commons.util.{ApiVersion, ScannedApiVersion} import java.util.Date @@ -160,6 +160,27 @@ case class PostAtmJsonV510 ( phone: String ) +case class PostCounterpartyLimitV510( + max_single_amount: Int, + max_monthly_amount: Int, + max_number_of_monthly_transactions: Int, + max_yearly_amount: Int, + max_number_of_yearly_transactions: Int +) + +case class CounterpartyLimitV510( + counterparty_limit_id: String, + bank_id: String, + account_id: String, + view_id: String, + counterparty_id: String, + max_single_amount: Int, + max_monthly_amount: Int, + max_number_of_monthly_transactions: Int, + max_yearly_amount: Int, + max_number_of_yearly_transactions: Int +) + case class AtmJsonV510 ( id : Option[String], bank_id : String, @@ -311,8 +332,77 @@ case class PostCreateUserAccountAccessJsonV510(username: String, provider:String case class PostAccountAccessJsonV510(user_id: String, view_id: String) +case class CreateCustomViewJson( + name: String, + description: String, + metadata_view: String, + is_public: Boolean, + which_alias_to_use: String, + hide_metadata_if_alias_used: Boolean, + allowed_permissions : List[String], +) { + def toCreateViewJson = CreateViewJson( + name: String, + description: String, + metadata_view: String, + is_public: Boolean, + which_alias_to_use: String, + hide_metadata_if_alias_used: Boolean, + allowed_actions = allowed_permissions, + ) +} + +case class UpdateCustomViewJson( + description: String, + metadata_view: String, + is_public: Boolean, + which_alias_to_use: String, + hide_metadata_if_alias_used: Boolean, + allowed_permissions: List[String] +) { + def toUpdateViewJson = UpdateViewJSON( + description: String, + metadata_view: String, + is_public: Boolean, + is_firehose= None, + which_alias_to_use: String, + hide_metadata_if_alias_used: Boolean, + allowed_actions = allowed_permissions + ) +} + +case class CustomViewJsonV510( + id: String, + name: String, + description: String, + metadata_view: String, + is_public: Boolean, + alias: String, + hide_metadata_if_alias_used: Boolean, + allowed_permissions: List[String] +) + object JSONFactory510 extends CustomJsonFormats { + def createViewJson(view: View): CustomViewJsonV510 = { + val alias = + if (view.usePublicAliasIfOneExists) + "public" + else if (view.usePrivateAliasIfOneExists) + "private" + else + "" + CustomViewJsonV510( + id = view.viewId.value, + name = stringOrNull(view.name), + description = stringOrNull(view.description), + metadata_view = view.metadataView, + is_public = view.isPublic, + alias = alias, + hide_metadata_if_alias_used = view.hideOtherAccountMetadataIfAlias, + allowed_permissions = APIUtil.getViewPermissions(view.asInstanceOf[ViewDefinition]).toList + ) + } def createCustomersIds(customers : List[Customer]): CustomersIdsJsonV510 = CustomersIdsJsonV510(customers.map(x => CustomerIdJson(x.customerId))) diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index d3f741354..a63916fe2 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -23,6 +23,7 @@ import code.bankconnectors.rest.RestConnector_vMar2019 import code.bankconnectors.storedprocedure.StoredProcedureConnector_vDec2019 import code.bankconnectors.vMay2019.KafkaMappedConnector_vMay2019 import code.bankconnectors.vSept2018.KafkaMappedConnector_vSept2018 +import code.counterpartylimit.{CounterpartyLimit, CounterpartyLimitTrait} import code.customeraccountlinks.CustomerAccountLinkTrait import code.endpointTag.EndpointTagT import code.fx.fx.TTL @@ -78,25 +79,27 @@ Could consider a Map of ("resourceType" -> "provider") - this could tell us whic */ object Connector extends SimpleInjector { - - val nameToConnector: Map[String, () => Connector] = Map( - "mapped" -> lazyValue(LocalMappedConnector), - "akka_vDec2018" -> lazyValue(AkkaConnector_vDec2018), - "kafka_vSept2018" -> lazyValue(KafkaMappedConnector_vSept2018), - "kafka_vMay2019" -> lazyValue(KafkaMappedConnector_vMay2019), - "rest_vMar2019" -> lazyValue(RestConnector_vMar2019), - "stored_procedure_vDec2019" -> lazyValue(StoredProcedureConnector_vDec2019), + // An object is a class that has exactly one instance. It is created lazily when it is referenced, like a lazy val. + // As a top-level value, an object is a singleton. + // As a member of an enclosing class or as a local value, it behaves exactly like a lazy val. + // Previously the right hand part was surrounded by Functions.lazyValue function + val nameToConnector: Map[String, Connector] = Map( + "mapped" -> LocalMappedConnector, + "akka_vDec2018" -> AkkaConnector_vDec2018, + "kafka_vSept2018" -> KafkaMappedConnector_vSept2018, + "kafka_vMay2019" -> KafkaMappedConnector_vMay2019, + "rest_vMar2019" -> RestConnector_vMar2019, + "stored_procedure_vDec2019" -> StoredProcedureConnector_vDec2019, // this proxy connector only for unit test, can set connector=proxy in test.default.props, but never set it in default.props - "proxy" -> lazyValue(ConnectorUtils.proxyConnector), - "internal" -> lazyValue(InternalConnector.instance) + "proxy" -> ConnectorUtils.proxyConnector, + "internal" -> InternalConnector.instance ) def getConnectorInstance(connectorVersion: String): Connector = { connectorVersion match { case "star" => StarConnector case k => nameToConnector.get(k) - .map(f => f()) - .getOrElse(throw new RuntimeException(s"Do not Support this connector version: $k")) + .getOrElse(throw new RuntimeException(s"$InvalidConnector Current Input is $k")) } } @@ -2662,5 +2665,34 @@ trait Connector extends MdcLoggable { def updateCustomerAccountLinkById(customerAccountLinkId: String, relationshipType: String, callContext: Option[CallContext]): OBPReturnType[Box[CustomerAccountLinkTrait]] = Future{(Failure(setUnimplementedError), callContext)} def getConsentImplicitSCA(user: User, callContext: Option[CallContext]): OBPReturnType[Box[ConsentImplicitSCAT]] = Future{(Failure(setUnimplementedError), callContext)} + + def createOrUpdateCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + maxSingleAmount: Int, + maxMonthlyAmount: Int, + maxNumberOfMonthlyTransactions: Int, + maxYearlyAmount: Int, + maxNumberOfYearlyTransactions: Int, callContext: Option[CallContext] + ): OBPReturnType[Box[CounterpartyLimitTrait]] = Future{(Failure(setUnimplementedError), callContext)} + def getCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[CounterpartyLimitTrait]] = Future{(Failure(setUnimplementedError), callContext)} + + def deleteCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError), callContext)} + + } diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index aa30a7834..c5bc13cb2 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -30,6 +30,7 @@ import code.branches.MappedBranch import code.cardattribute.CardAttributeX import code.cards.MappedPhysicalCard import code.context.{UserAuthContextProvider, UserAuthContextUpdateProvider} +import code.counterpartylimit.{CounterpartyLimit, CounterpartyLimitProvider, CounterpartyLimitTrait} import code.customer._ import code.customeraccountlinks.CustomerAccountLinkTrait import code.customeraddress.CustomerAddressX @@ -5911,5 +5912,58 @@ object LocalMappedConnector extends Connector with MdcLoggable { )), callContext) } + override def createOrUpdateCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + maxSingleAmount: Int, + maxMonthlyAmount: Int, + maxNumberOfMonthlyTransactions: Int, + maxYearlyAmount: Int, + maxNumberOfYearlyTransactions: Int, callContext: Option[CallContext]) = + CounterpartyLimitProvider.counterpartyLimit.vend.createOrUpdateCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + maxSingleAmount: Int, + maxMonthlyAmount: Int, + maxNumberOfMonthlyTransactions: Int, + maxYearlyAmount: Int, + maxNumberOfYearlyTransactions: Int) map { + (_, callContext) + } + + override def getCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + callContext: Option[CallContext] + ) = + CounterpartyLimitProvider.counterpartyLimit.vend.getCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String + ) map { + (_, callContext) + } + + override def deleteCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + callContext: Option[CallContext] + ): OBPReturnType[Box[Boolean]] = + CounterpartyLimitProvider.counterpartyLimit.vend.deleteCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String) map { + (_, callContext) + } } diff --git a/obp-api/src/main/scala/code/consent/ConsentProvider.scala b/obp-api/src/main/scala/code/consent/ConsentProvider.scala index 1641adfa4..2a3fdf52e 100644 --- a/obp-api/src/main/scala/code/consent/ConsentProvider.scala +++ b/obp-api/src/main/scala/code/consent/ConsentProvider.scala @@ -21,7 +21,7 @@ trait ConsentProvider { def updateConsentStatus(consentId: String, status: ConsentStatus): Box[MappedConsent] def updateConsentUser(consentId: String, user: User): Box[MappedConsent] def getConsentsByUser(userId: String): List[MappedConsent] - def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String]): Box[MappedConsent] + def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String], consumer: Option[Consumer] = None): Box[MappedConsent] def setJsonWebToken(consentId: String, jwt: String): Box[MappedConsent] def revoke(consentId: String): Box[MappedConsent] def checkAnswer(consentId: String, challenge: String): Box[MappedConsent] diff --git a/obp-api/src/main/scala/code/consent/MappedConsent.scala b/obp-api/src/main/scala/code/consent/MappedConsent.scala index 6fe27b370..37734773d 100644 --- a/obp-api/src/main/scala/code/consent/MappedConsent.scala +++ b/obp-api/src/main/scala/code/consent/MappedConsent.scala @@ -62,13 +62,14 @@ object MappedConsentProvider extends ConsentProvider { override def getConsentsByUser(userId: String): List[MappedConsent] = { MappedConsent.findAll(By(MappedConsent.mUserId, userId)) } - override def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String]): Box[MappedConsent] = { + override def createObpConsent(user: User, challengeAnswer: String, consentRequestId:Option[String], consumer: Option[Consumer]): Box[MappedConsent] = { tryo { val salt = BCrypt.gensalt() val challengeAnswerHashed = BCrypt.hashpw(challengeAnswer, salt).substring(0, 44) MappedConsent .create .mUserId(user.userId) + .mConsumerId(consumer.map(_.consumerId.get).getOrElse(null)) .mConsentRequestId(consentRequestId.getOrElse(null)) .mChallenge(challengeAnswerHashed) .mSalt(salt) diff --git a/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala b/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala index 25725e19f..4d4babfd6 100644 --- a/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala +++ b/obp-api/src/main/scala/code/consumer/ConsumerProvider.scala @@ -28,6 +28,7 @@ trait ConsumersProvider { def getConsumerByPrimaryId(id: Long): Box[Consumer] def getConsumerByConsumerKey(consumerKey: String): Box[Consumer] def getConsumerByConsumerKeyFuture(consumerKey: String): Future[Box[Consumer]] + def getConsumerByPemCertificate(pem: String): Box[Consumer] def getConsumerByConsumerId(consumerId: String): Box[Consumer] def getConsumerByConsumerIdFuture(consumerId: String): Future[Box[Consumer]] def getConsumersByUserIdFuture(userId: String): Future[List[Consumer]] @@ -61,6 +62,7 @@ class RemotedataConsumersCaseClasses { case class getConsumerByPrimaryId(id: Long) case class getConsumerByConsumerKey(consumerKey: String) case class getConsumerByConsumerKeyFuture(consumerKey: String) + case class getConsumerByPemCertificate(pem: String) case class getConsumerByConsumerId(consumerId: String) case class getConsumerByConsumerIdFuture(consumerId: String) case class getConsumersByUserIdFuture(userId: String) diff --git a/obp-api/src/main/scala/code/counterpartylimit/CounterpartyLimit.scala b/obp-api/src/main/scala/code/counterpartylimit/CounterpartyLimit.scala new file mode 100644 index 000000000..92bc5f559 --- /dev/null +++ b/obp-api/src/main/scala/code/counterpartylimit/CounterpartyLimit.scala @@ -0,0 +1,57 @@ +package code.counterpartylimit + +import code.api.util.APIUtil +import com.openbankproject.commons.util.JsonAble +import net.liftweb.util.SimpleInjector +import net.liftweb.common.Box +import scala.concurrent.Future + +object CounterpartyLimitProvider extends SimpleInjector { + val counterpartyLimit = new Inject(buildOne _) {} + def buildOne: CounterpartyLimitProviderTrait = APIUtil.getPropsAsBoolValue("use_akka", false) match { + case _ => MappedCounterpartyLimitProvider +// case true => RemotedataCounterpartyLimit // we are getting rid of the akka now. so do not implement it here + } +} + +trait CounterpartyLimitProviderTrait { + def getCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String + ): Future[Box[CounterpartyLimitTrait]] + + def deleteCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String + ): Future[Box[Boolean]] + + def createOrUpdateCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + maxSingleAmount: Int, + maxMonthlyAmount: Int, + maxNumberOfMonthlyTransactions: Int, + maxYearlyAmount: Int, + maxNumberOfYearlyTransactions: Int): Future[Box[CounterpartyLimitTrait]] +} + +trait CounterpartyLimitTrait extends JsonAble{ + def counterpartyLimitId: String + def bankId: String + def accountId: String + def viewId: String + def counterpartyId: String + + def maxSingleAmount: Int + def maxMonthlyAmount: Int + def maxNumberOfMonthlyTransactions: Int + def maxYearlyAmount: Int + def maxNumberOfYearlyTransactions: Int +} + diff --git a/obp-api/src/main/scala/code/counterpartylimit/MappedCounterpartyLimit.scala b/obp-api/src/main/scala/code/counterpartylimit/MappedCounterpartyLimit.scala new file mode 100644 index 000000000..a919f3c96 --- /dev/null +++ b/obp-api/src/main/scala/code/counterpartylimit/MappedCounterpartyLimit.scala @@ -0,0 +1,149 @@ +package code.counterpartylimit + +import code.util.MappedUUID +import net.liftweb.common.{Box, Full} +import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo +import com.openbankproject.commons.ExecutionContext.Implicits.global +import net.liftweb.json +import net.liftweb.json.Formats +import net.liftweb.json.JsonAST.{JValue,JString} +import net.liftweb.json.JsonDSL._ +import scala.concurrent.Future + +object MappedCounterpartyLimitProvider extends CounterpartyLimitProviderTrait { + + def getCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String + ): Future[Box[CounterpartyLimitTrait]] = Future { + CounterpartyLimit.find( + By(CounterpartyLimit.BankId, bankId), + By(CounterpartyLimit.AccountId, accountId), + By(CounterpartyLimit.ViewId, viewId), + By(CounterpartyLimit.CounterpartyId, counterpartyId) + ) + } + + def deleteCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String + ): Future[Box[Boolean]] = Future { + CounterpartyLimit.find( + By(CounterpartyLimit.BankId, bankId), + By(CounterpartyLimit.AccountId, accountId), + By(CounterpartyLimit.ViewId, viewId), + By(CounterpartyLimit.CounterpartyId, counterpartyId) + ).map(_.delete_!) + } + + def createOrUpdateCounterpartyLimit( + bankId: String, + accountId: String, + viewId: String, + counterpartyId: String, + maxSingleAmount: Int, + maxMonthlyAmount: Int, + maxNumberOfMonthlyTransactions: Int, + maxYearlyAmount: Int, + maxNumberOfYearlyTransactions: Int)= Future { + + def createCounterpartyLimit(counterpartyLimit: CounterpartyLimit)= { + tryo { + counterpartyLimit.BankId(bankId) + counterpartyLimit.AccountId(accountId) + counterpartyLimit.ViewId(viewId) + counterpartyLimit.CounterpartyId(counterpartyId) + counterpartyLimit.MaxSingleAmount(maxSingleAmount) + counterpartyLimit.MaxMonthlyAmount(maxMonthlyAmount) + counterpartyLimit.MaxNumberOfMonthlyTransactions(maxNumberOfMonthlyTransactions) + counterpartyLimit.MaxYearlyAmount(maxYearlyAmount) + counterpartyLimit.MaxNumberOfYearlyTransactions(maxNumberOfYearlyTransactions) + counterpartyLimit.saveMe() + } + } + + def getCounterpartyLimit = CounterpartyLimit.find( + By(CounterpartyLimit.BankId, bankId), + By(CounterpartyLimit.AccountId, accountId), + By(CounterpartyLimit.ViewId, viewId), + By(CounterpartyLimit.CounterpartyId, counterpartyId), + ) + + val result = getCounterpartyLimit match { + case Full(counterpartyLimit) => createCounterpartyLimit(counterpartyLimit) + case _ => createCounterpartyLimit(CounterpartyLimit.create) + } + result + } +} + +class CounterpartyLimit extends CounterpartyLimitTrait with LongKeyedMapper[CounterpartyLimit] with IdPK with CreatedUpdated { + override def getSingleton = CounterpartyLimit + + object CounterpartyLimitId extends MappedUUID(this) + + object BankId extends MappedString(this, 255){ + override def dbNotNull_? = true + } + object AccountId extends MappedString(this, 255){ + override def dbNotNull_? = true + } + object ViewId extends MappedString(this, 255){ + override def dbNotNull_? = true + } + object CounterpartyId extends MappedString(this, 255){ + override def dbNotNull_? = true + } + + + object MaxSingleAmount extends MappedInt(this) { + override def defaultValue = -1 + } + object MaxMonthlyAmount extends MappedInt(this) { + override def defaultValue = -1 + } + object MaxNumberOfMonthlyTransactions extends MappedInt(this) { + override def defaultValue = -1 + } + object MaxYearlyAmount extends MappedInt(this) { + override def defaultValue = -1 + } + object MaxNumberOfYearlyTransactions extends MappedInt(this) { + override def defaultValue = -1 + } + + def counterpartyLimitId: String = CounterpartyLimitId.get + + def bankId: String = BankId.get + def accountId: String = AccountId.get + def viewId: String = ViewId.get + def counterpartyId: String = CounterpartyId.get + + def maxSingleAmount: Int = MaxSingleAmount.get + def maxMonthlyAmount: Int = MaxMonthlyAmount.get + def maxNumberOfMonthlyTransactions: Int = MaxNumberOfMonthlyTransactions.get + def maxYearlyAmount: Int = MaxYearlyAmount.get + def maxNumberOfYearlyTransactions: Int = MaxNumberOfYearlyTransactions.get + + override def toJValue(implicit format: Formats): JValue ={ + ("counterparty_limit_id", counterpartyLimitId) ~ + ("bank_id", bankId) ~ + ("account_id",accountId) ~ + ("view_id",viewId) ~ + ("counterparty_id",counterpartyId) ~ + ("max_single_amount", maxSingleAmount) ~ + ("max_monthly_amount", maxMonthlyAmount) ~ + ("max_number_of_monthly_transactions", maxNumberOfMonthlyTransactions) ~ + ("max_yearly_amount", maxYearlyAmount) ~ + ("max_number_of_yearly_transactions", maxNumberOfYearlyTransactions) + } +} + +object CounterpartyLimit extends CounterpartyLimit with LongKeyedMetaMapper[CounterpartyLimit] { + override def dbIndexes = UniqueIndex(CounterpartyLimitId) :: UniqueIndex(BankId, AccountId, ViewId, CounterpartyId) :: super.dbIndexes +} diff --git a/obp-api/src/main/scala/code/model/OAuth.scala b/obp-api/src/main/scala/code/model/OAuth.scala index 14da03e21..abc410f9c 100644 --- a/obp-api/src/main/scala/code/model/OAuth.scala +++ b/obp-api/src/main/scala/code/model/OAuth.scala @@ -103,6 +103,10 @@ object MappedConsumersProvider extends ConsumersProvider with MdcLoggable { } } + def getConsumerByPemCertificate(pem: String): Box[Consumer] = { + Consumer.find(By(Consumer.clientCertificate, pem)) + } + def getConsumerByConsumerId(consumerId: String): Box[Consumer] = { Consumer.find(By(Consumer.consumerId, consumerId)) } diff --git a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala index 9b7b9bd48..6e2492089 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -1038,14 +1038,34 @@ def restoreSomeSessions(): Unit = { override def login: NodeSeq = { // This query parameter is specific to ORY Hydra login request val loginChallenge: Box[String] = ObpS.param("login_challenge").or(S.getSessionAttribute("login_challenge")) - def redirectUri(): String = { - loginRedirect.get match { - case Full(url) => - loginRedirect(Empty) - url - case _ => - homePage + def redirectUri(user: Box[ResourceUser]): String = { + val userId = user.map(_.userId).getOrElse("") + val hashedAgreementTextOfUser = + UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, "terms_and_conditions") + .map(_.agreementHash).getOrElse(HashUtil.Sha256Hash("not set")) + val agreementText = getWebUiPropsValue("webui_terms_and_conditions", "not set") + val hashedAgreementText = HashUtil.Sha256Hash(agreementText) + if(hashedAgreementTextOfUser == hashedAgreementText) { // Chech terms and conditions + val hashedAgreementTextOfUser = + UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, "privacy_conditions") + .map(_.agreementHash).getOrElse(HashUtil.Sha256Hash("not set")) + val agreementText = getWebUiPropsValue("webui_privacy_policy", "not set") + val hashedAgreementText = HashUtil.Sha256Hash(agreementText) + if(hashedAgreementTextOfUser == hashedAgreementText) { // Check privacy policy + loginRedirect.get match { + case Full(url) => + loginRedirect(Empty) + url + case _ => + homePage + } + } else { + "/privacy-policy" + } + } else { + "/terms-and-conditions" } + } //Check the internal redirect, in case for open redirect issue. // variable redirect is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code: @@ -1131,7 +1151,7 @@ def restoreSomeSessions(): Unit = { // User init actions AfterApiAuth.innerLoginUserInitAction(Full(user)) logger.info("login redirect: " + loginRedirect.get) - val redirect = redirectUri() + val redirect = redirectUri(user.user.foreign) checkInternalRedirectAndLogUserIn(preLoginState, redirect, user) } else { // If user is NOT locked AND password is wrong => increment bad login attempt counter. LoginAttempt.incrementBadLoginAttempts(user.getProvider(),usernameFromGui) @@ -1151,7 +1171,7 @@ def restoreSomeSessions(): Unit = { LoginAttempt.resetBadLoginAttempts(user.getProvider(), usernameFromGui) val preLoginState = capturePreLoginState() logger.info("login redirect: " + loginRedirect.get) - val redirect = redirectUri() + val redirect = redirectUri(user.user.foreign) //This method is used for connector = kafka* || obpjvm* //It will update the views and createAccountHolder .... registeredUserHelper(user.getProvider(),user.username.get) @@ -1176,7 +1196,7 @@ def restoreSomeSessions(): Unit = { val preLoginState = capturePreLoginState() logger.info("login redirect: " + loginRedirect.get) - val redirect = redirectUri() + val redirect = redirectUri(user.foreign) externalUserHelper(usernameFromGui, passwordFromGui) match { case Full(user: AuthUser) => LoginAttempt.resetBadLoginAttempts(user.getProvider(), usernameFromGui) diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataConsumers.scala b/obp-api/src/main/scala/code/remotedata/RemotedataConsumers.scala index 714644782..ccb41bdee 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataConsumers.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataConsumers.scala @@ -20,6 +20,9 @@ object RemotedataConsumers extends ObpActorInit with ConsumersProvider { def getConsumerByPrimaryId(id: Long): Box[Consumer] = getValueFromFuture( (actor ? cc.getConsumerByPrimaryId(id)).mapTo[Box[Consumer]] ) + def getConsumerByPemCertificate(pem: String): Box[Consumer] = getValueFromFuture( + (actor ? cc.getConsumerByPemCertificate(pem)).mapTo[Box[Consumer]] + ) def getConsumerByConsumerId(consumerId: String): Box[Consumer] = getValueFromFuture( (actor ? cc.getConsumerByConsumerId(consumerId)).mapTo[Box[Consumer]] ) diff --git a/obp-api/src/main/scala/code/remotedata/RemotedataViews.scala b/obp-api/src/main/scala/code/remotedata/RemotedataViews.scala index cef608ac0..a969510fd 100644 --- a/obp-api/src/main/scala/code/remotedata/RemotedataViews.scala +++ b/obp-api/src/main/scala/code/remotedata/RemotedataViews.scala @@ -119,6 +119,9 @@ object RemotedataViews extends ObpActorInit with Views { def privateViewsUserCanAccessAtBank(user: User, bankId: BankId): (List[View], List[AccountAccess]) = getValueFromFuture( (actor ? cc.privateViewsUserCanAccessAtBank(user, bankId)).mapTo[(List[View], List[AccountAccess])] ) + def getAccountAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId): ((List[View], List[AccountAccess])) = getValueFromFuture( + (actor ? cc.privateViewsUserCanAccessAtBankThroughView(user, bankId, viewId)).mapTo[(List[View], List[AccountAccess])] + ) def privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId): List[View] = getValueFromFuture( (actor ? cc.privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId)).mapTo[List[View]] diff --git a/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala b/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala new file mode 100644 index 000000000..1ff3fd0e7 --- /dev/null +++ b/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala @@ -0,0 +1,77 @@ +/** +Open Bank Project - API +Copyright (C) 2011-2019, TESOBE GmbH. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Email: contact@tesobe.com +TESOBE GmbH. +Osloer Strasse 16/17 +Berlin 13359, Germany + +This product includes software developed at +TESOBE (http://www.tesobe.com/) + + */ +package code.snippet + +import code.model.dataAccess.AuthUser +import code.users.UserAgreementProvider +import code.util.Helper +import code.util.Helper.MdcLoggable +import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue +import net.liftweb.http.{S, SHtml} +import net.liftweb.util.CssSel +import net.liftweb.util.Helpers._ + +class PrivacyPolicy extends MdcLoggable { + + def updateForm: CssSel = { + + def submitButtonDefense(): Unit = { + updateUserAgreement() + } + + def skipButtonDefense(): Unit = { + S.redirectTo("/") + } + + def displayContent = { + if(AuthUser.currentUser.isDefined) { + "block" + } else { + "none" + } + } + + def update = { + val username = AuthUser.currentUser.flatMap(_.user.foreign.map(_.name)).getOrElse("") + "#privacy-policy-username *" #> username & + "type=submit" #> SHtml.submit(s"${Helper.i18n("outdated.terms.button.accept")}", () => submitButtonDefense) & + "type=reset" #> SHtml.submit(s"${Helper.i18n("outdated.terms.button.skip")}", () => skipButtonDefense) & + "#form_privacy_policy [style]" #> s"display: $displayContent;" + } + update + } + + private def updateUserAgreement() = { + if(AuthUser.currentUser.isDefined) { + val agreementText = getWebUiPropsValue("webui_privacy_policy", "not set") + UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + AuthUser.currentUser.flatMap(_.user.foreign.map(_.userId)).getOrElse(""), "privacy_conditions", agreementText) + S.redirectTo("/") + } + } + +} diff --git a/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala b/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala new file mode 100644 index 000000000..5bbaa754a --- /dev/null +++ b/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala @@ -0,0 +1,78 @@ +/** +Open Bank Project - API +Copyright (C) 2011-2019, TESOBE GmbH. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Email: contact@tesobe.com +TESOBE GmbH. +Osloer Strasse 16/17 +Berlin 13359, Germany + +This product includes software developed at +TESOBE (http://www.tesobe.com/) + + */ +package code.snippet + +import code.model.dataAccess.AuthUser +import code.users.UserAgreementProvider +import code.util.Helper +import code.util.Helper.MdcLoggable +import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue +import net.liftweb.http.{S, SHtml} +import net.liftweb.util.CssSel +import net.liftweb.util.Helpers._ + +class TermsAndConditions extends MdcLoggable { + + def updateForm: CssSel = { + + def submitButtonDefense(): Unit = { + updateUserAgreement() + } + + def skipButtonDefense(): Unit = { + S.redirectTo("/") + } + + def displayContent = { + if(AuthUser.currentUser.isDefined) { + "block" + } else { + "none" + } + } + + def update = { + val username = AuthUser.currentUser.flatMap(_.user.foreign.map(_.name)).getOrElse("") + "#terms-and-conditions-username *" #> username & + "type=submit" #> SHtml.submit(s"${Helper.i18n("outdated.policy.button.accept")}", () => submitButtonDefense) & + "type=reset" #> SHtml.submit(s"${Helper.i18n("outdated.policy.button.skip")}", () => skipButtonDefense) & + "#form_terms_and_conditions [style]" #> s"display: $displayContent;" + } + update + } + + private def updateUserAgreement() = { + if(AuthUser.currentUser.isDefined) { + val agreementText = getWebUiPropsValue("webui_terms_and_conditions", "not set") + // val hashedAgreementText = HashUtil.Sha256Hash(agreementText) + UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + AuthUser.currentUser.flatMap(_.user.foreign.map(_.userId)).getOrElse(""), "terms_and_conditions", agreementText) + S.redirectTo("/") + } + } + +} diff --git a/obp-api/src/main/scala/code/snippet/WebUI.scala b/obp-api/src/main/scala/code/snippet/WebUI.scala index be6696458..1d2acb252 100644 --- a/obp-api/src/main/scala/code/snippet/WebUI.scala +++ b/obp-api/src/main/scala/code/snippet/WebUI.scala @@ -328,12 +328,7 @@ class WebUI extends MdcLoggable{ def sandboxIntroductionLink: CssSel = { val webUiApiDocumentation = getWebUiPropsValue("webui_api_documentation_url",s"${getServerUrl}/introduction") - val apiDocumentation = - if (webUiApiDocumentation == s"${getServerUrl}/introduction") - webUiApiDocumentation - else - webUiApiDocumentation + "#Sandbox-Introduction" - "#sandbox-introduction-link [href]" #> scala.xml.Unparsed(apiDocumentation) + "#sandbox-introduction-link [href]" #> scala.xml.Unparsed(webUiApiDocumentation) } def subscriptionsButton: CssSel = { diff --git a/obp-api/src/main/scala/code/users/UserAgreement.scala b/obp-api/src/main/scala/code/users/UserAgreement.scala index 19a643279..e4927e0e9 100644 --- a/obp-api/src/main/scala/code/users/UserAgreement.scala +++ b/obp-api/src/main/scala/code/users/UserAgreement.scala @@ -7,6 +7,7 @@ import code.api.util.HashUtil import code.util.UUIDString import net.liftweb.common.{Box, Empty, Full} import net.liftweb.mapper._ +import net.liftweb.common.Box.tryo object MappedUserAgreementProvider extends UserAgreementProvider { override def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = { @@ -75,5 +76,13 @@ class UserAgreement extends UserAgreementTrait with LongKeyedMapper[UserAgreemen object UserAgreement extends UserAgreement with LongKeyedMetaMapper[UserAgreement] { override def dbIndexes: List[BaseIndex[UserAgreement]] = UniqueIndex(UserAgreementId) :: super.dbIndexes + override def beforeSave = List( + agreement => + tryo { + val hash = HashUtil.Sha256Hash(agreement.agreementText) + agreement.AgreementHash(hash ).save + } + ) + } diff --git a/obp-api/src/main/scala/code/util/Helper.scala b/obp-api/src/main/scala/code/util/Helper.scala index be134a3c3..7296aafbd 100644 --- a/obp-api/src/main/scala/code/util/Helper.scala +++ b/obp-api/src/main/scala/code/util/Helper.scala @@ -211,7 +211,12 @@ object Helper extends Loggable { */ def isValidInternalRedirectUrl(url: String) : Boolean = { //set the default value is "/" and "/oauth/authorize" - val validUrls = List("/","/oauth/authorize","/consumer-registration","/dummy-user-tokens","/create-sandbox-account", "/add-user-auth-context-update-request","/otp") + val validUrls = List( + "/","/oauth/authorize","/consumer-registration", + "/dummy-user-tokens","/create-sandbox-account", + "/add-user-auth-context-update-request","/otp", + "/terms-and-conditions", "/privacy-policy" + ) //case1: OBP-API login: url = "/" //case2: API-Explore oauth login: url = "/oauth/authorize?oauth_token=V0JTCDYXWUNTXDZ3VUDNM1HE3Q1PZR2WJ4PURXQA&logUserOut=false" diff --git a/obp-api/src/main/scala/code/views/MapperViews.scala b/obp-api/src/main/scala/code/views/MapperViews.scala index c4bc494ed..1fe2549b7 100644 --- a/obp-api/src/main/scala/code/views/MapperViews.scala +++ b/obp-api/src/main/scala/code/views/MapperViews.scala @@ -9,7 +9,7 @@ import code.api.util.APIUtil._ import code.api.util.ErrorMessages._ import code.util.Helper.MdcLoggable import code.views.system.ViewDefinition.create -import code.views.system.{AccountAccess, ViewDefinition} +import code.views.system.{AccountAccess, ViewDefinition, ViewPermission} import com.openbankproject.commons.model.{UpdateViewJSON, _} import net.liftweb.common._ import net.liftweb.mapper.{Ascending, By, ByList, NullRef, OrderBy, PreCache, Schemifier} @@ -399,7 +399,7 @@ object MapperViews extends Views with MdcLoggable { existing match { case true => - Failure(s"$ExistingSystemViewError $viewId") + Failure(s"$SystemViewAlreadyExistsError Current VIEW_ID($viewId)") case false => val createdView = ViewDefinition.create.name_(view.name).view_id(viewId) createdView.setFromViewData(view) @@ -436,7 +436,7 @@ object MapperViews extends Views with MdcLoggable { ) == 1 if (existing) - Failure(s"There is already a view with permalink $viewId on this bank account") + Failure(s"$CustomViewAlreadyExistsError Current BankId(${bankAccountId.bankId.value}), AccountId(${bankAccountId.accountId.value}), ViewId($viewId).") else { val createdView = ViewDefinition.create. name_(view.name). @@ -584,6 +584,17 @@ object MapperViews extends Views with MdcLoggable { }) PrivateViewsUserCanAccessCommon(accountAccess) } + def getAccountAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId): (List[View], List[AccountAccess]) ={ + val accountAccess = AccountAccess.findAll( + By(AccountAccess.user_fk, user.userPrimaryKey.value), + By(AccountAccess.bank_id, bankId.value), + By(AccountAccess.view_id, viewId.value) + ).filter(accountAccess => { + val view = getViewFromAccountAccess(accountAccess) + view.isDefined && view.map(_.isPrivate) == Full(true) + }) + PrivateViewsUserCanAccessCommon(accountAccess) + } private def PrivateViewsUserCanAccessCommon(accountAccess: List[AccountAccess]): (List[ViewDefinition], List[AccountAccess]) = { val listOfTuples: List[(AccountAccess, Box[ViewDefinition])] = accountAccess.map( @@ -622,11 +633,126 @@ object MapperViews extends Views with MdcLoggable { logger.debug(s"-->getOrCreateSystemViewFromCbs --- finish.${viewId } : ${theView} ") theView } + + private def migrateViewPermissions(view: View): Unit = { + val permissionNames = List( + "canSeeTransactionOtherBankAccount", + "canSeeTransactionMetadata", + "canSeeTransactionDescription", + "canSeeTransactionAmount", + "canSeeTransactionType", + "canSeeTransactionCurrency", + "canSeeTransactionStartDate", + "canSeeTransactionFinishDate", + "canSeeTransactionBalance", + "canSeeComments", + "canSeeOwnerComment", + "canSeeTags", + "canSeeImages", + "canSeeBankAccountOwners", + "canSeeBankAccountType", + "canSeeBankAccountBalance", + "canQueryAvailableFunds", + "canSeeBankAccountLabel", + "canSeeBankAccountNationalIdentifier", + "canSeeBankAccountSwift_bic", + "canSeeBankAccountIban", + "canSeeBankAccountNumber", + "canSeeBankAccountBankName", + "canSeeBankAccountBankPermalink", + "canSeeBankRoutingScheme", + "canSeeBankRoutingAddress", + "canSeeBankAccountRoutingScheme", + "canSeeBankAccountRoutingAddress", + "canSeeOtherAccountNationalIdentifier", + "canSeeOtherAccountSWIFT_BIC", + "canSeeOtherAccountIBAN", + "canSeeOtherAccountBankName", + "canSeeOtherAccountNumber", + "canSeeOtherAccountMetadata", + "canSeeOtherAccountKind", + "canSeeOtherBankRoutingScheme", + "canSeeOtherBankRoutingAddress", + "canSeeOtherAccountRoutingScheme", + "canSeeOtherAccountRoutingAddress", + "canSeeMoreInfo", + "canSeeUrl", + "canSeeImageUrl", + "canSeeOpenCorporatesUrl", + "canSeeCorporateLocation", + "canSeePhysicalLocation", + "canSeePublicAlias", + "canSeePrivateAlias", + "canAddMoreInfo", + "canAddURL", + "canAddImageURL", + "canAddOpenCorporatesUrl", + "canAddCorporateLocation", + "canAddPhysicalLocation", + "canAddPublicAlias", + "canAddPrivateAlias", + "canAddCounterparty", + "canGetCounterparty", + "canDeleteCounterparty", + "canDeleteCorporateLocation", + "canDeletePhysicalLocation", + "canEditOwnerComment", + "canAddComment", + "canDeleteComment", + "canAddTag", + "canDeleteTag", + "canAddImage", + "canDeleteImage", + "canAddWhereTag", + "canSeeWhereTag", + "canDeleteWhereTag", + "canAddTransactionRequestToOwnAccount", + "canAddTransactionRequestToAnyAccount", + "canSeeBankAccountCreditLimit", + "canCreateDirectDebit", + "canCreateStandingOrder", + "canRevokeAccessToCustomViews", + "canGrantAccessToCustomViews", + "canSeeTransactionRequests", + "canSeeTransactionRequestTypes", + "canSeeAvailableViewsForBankAccount", + "canUpdateBankAccountLabel", + "canCreateCustomView", + "canDeleteCustomView", + "canUpdateCustomView", + "canSeeViewsWithPermissionsForAllUsers", + "canSeeViewsWithPermissionsForOneUser" + ) + + permissionNames.foreach { permissionName => + // Get permission value + val permissionValue = view.getClass.getMethod(permissionName).invoke(view).asInstanceOf[Boolean] + + ViewPermission.findViewPermissions(view.viewId).find(_.permission.get == permissionName) match { + case Some(permission) if !permissionValue => + ViewPermission.delete_!(permission) + case Some(permission) if permissionValue => + // View definition is in accordance with View permission + case _ => + ViewPermission.create + .bank_id(null) + .account_id(null) + .view_id(view.viewId.value) + .permission(permissionName) + .save + } + } + } def getOrCreateSystemView(viewId: String) : Box[View] = { getExistingSystemView(viewId) match { - case Empty => createDefaultSystemView(viewId) - case Full(v) => Full(v) + case Empty => + val view = createDefaultSystemView(viewId) + view.map(v => migrateViewPermissions(v)) + view + case Full(v) => + migrateViewPermissions(v) + Full(v) case Failure(msg, t, c) => Failure(msg, t, c) case ParamFailure(x,y,z,q) => ParamFailure(x,y,z,q) } diff --git a/obp-api/src/main/scala/code/views/Views.scala b/obp-api/src/main/scala/code/views/Views.scala index 49360e057..6934281c9 100644 --- a/obp-api/src/main/scala/code/views/Views.scala +++ b/obp-api/src/main/scala/code/views/Views.scala @@ -84,6 +84,7 @@ trait Views { def privateViewsUserCanAccess(user: User): (List[View], List[AccountAccess]) def privateViewsUserCanAccess(user: User, viewIds: List[ViewId]): (List[View], List[AccountAccess]) def privateViewsUserCanAccessAtBank(user: User, bankId: BankId): (List[View], List[AccountAccess]) + def getAccountAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId): (List[View], List[AccountAccess]) def privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId) : List[View] //the following return list[BankIdAccountId], just use the list[View] method, the View object contains enough data for it. @@ -150,6 +151,7 @@ class RemotedataViewsCaseClasses { case class privateViewsUserCanAccess(user: User) case class privateViewsUserCanAccessViaViewId(user: User, viewIds: List[ViewId]) case class privateViewsUserCanAccessAtBank(user: User, bankId: BankId) + case class privateViewsUserCanAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId) case class privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId) case class getAllFirehoseAccounts(bank: Bank, user : User) case class publicViews() diff --git a/obp-api/src/main/scala/code/views/system/ViewPermission.scala b/obp-api/src/main/scala/code/views/system/ViewPermission.scala new file mode 100644 index 000000000..5d3b11c10 --- /dev/null +++ b/obp-api/src/main/scala/code/views/system/ViewPermission.scala @@ -0,0 +1,29 @@ +package code.views.system + +import code.util.UUIDString +import com.openbankproject.commons.model._ +import net.liftweb.mapper._ +class ViewPermission extends LongKeyedMapper[ViewPermission] with IdPK with CreatedUpdated { + def getSingleton = ViewPermission + object bank_id extends MappedString(this, 255) + object account_id extends MappedString(this, 255) + object view_id extends UUIDString(this) + object permission extends MappedString(this, 255) +} +object ViewPermission extends ViewPermission with LongKeyedMetaMapper[ViewPermission] { + override def dbIndexes: List[BaseIndex[ViewPermission]] = UniqueIndex(bank_id, account_id, view_id, permission) :: super.dbIndexes + + // Very working progress + def findViewPermissions(bankId: BankId, accountId: AccountId, viewId: ViewId): List[ViewPermission] = + ViewPermission.findAll( + By(ViewPermission.bank_id, bankId.value), + By(ViewPermission.account_id, accountId.value), + By(ViewPermission.view_id, viewId.value) + ) // Very working progress + def findViewPermissions(viewId: ViewId): List[ViewPermission] = + ViewPermission.findAll( + NullRef(ViewPermission.bank_id), + NullRef(ViewPermission.account_id), + By(ViewPermission.view_id, viewId.value) + ) +} diff --git a/obp-api/src/main/webapp/main-faq.html b/obp-api/src/main/webapp/main-faq.html index 6192b9ce2..6a65bbecd 100644 --- a/obp-api/src/main/webapp/main-faq.html +++ b/obp-api/src/main/webapp/main-faq.html @@ -51,11 +51,10 @@

- There are two ways to authenticate a user:There are multiple ways to authenticate a user including: OAuth and Direct Login . If you - are using this sandbox for a hackathon, we recommend you use Direct Login to authenticate as it - is easier than the OAuth workflow. + are using this sandbox for a hackathon, we recommend you use Direct Login to start with.

@@ -78,10 +77,10 @@ href="">Direct Login. For an OAuth walkthrough example with sample code, please see here. - We use OAuth 1.0a. For deepish technical details of the flow We support OAuth 1.0a. For deepish technical details of the flow see here.
- We also support OAuth 2.0. For the technical details of using OBP API with OAuth 2.0, please We support OAuth 2.0. For the technical details of using OBP API with OAuth 2.0, please see here.

diff --git a/obp-api/src/main/webapp/privacy-policy.html b/obp-api/src/main/webapp/privacy-policy.html index d831ae849..683b9ad47 100644 --- a/obp-api/src/main/webapp/privacy-policy.html +++ b/obp-api/src/main/webapp/privacy-policy.html @@ -26,7 +26,18 @@ Berlin 13359, Germany
-
+
+
+
Dear . We have updated our policy since you last agreed to them! Please review the text and agree if you agree!
+
+ +
+
+ +
+
+
+
diff --git a/obp-api/src/main/webapp/terms-and-conditions.html b/obp-api/src/main/webapp/terms-and-conditions.html index 0c653baf8..714d93f00 100644 --- a/obp-api/src/main/webapp/terms-and-conditions.html +++ b/obp-api/src/main/webapp/terms-and-conditions.html @@ -26,7 +26,18 @@ Berlin 13359, Germany
-
+
+
+
Dear . We have updated our terms since you last agreed to them! Please review the text and agree if you agree!
+
+ +
+
+ +
+
+
+
diff --git a/obp-api/src/test/resources/cert/client.pfx b/obp-api/src/test/resources/cert/client.pfx deleted file mode 100644 index cb1159850..000000000 Binary files a/obp-api/src/test/resources/cert/client.pfx and /dev/null differ diff --git a/obp-api/src/test/resources/cert/localhost_SAN_dns_ip.pfx b/obp-api/src/test/resources/cert/localhost_SAN_dns_ip.pfx deleted file mode 100644 index 548fa6e82..000000000 Binary files a/obp-api/src/test/resources/cert/localhost_SAN_dns_ip.pfx and /dev/null differ diff --git a/obp-api/src/test/resources/cert/localhost_san_dns_ip.pfx b/obp-api/src/test/resources/cert/localhost_san_dns_ip.pfx new file mode 100644 index 000000000..4f1cf3344 Binary files /dev/null and b/obp-api/src/test/resources/cert/localhost_san_dns_ip.pfx differ diff --git a/obp-api/src/test/resources/frozen_type_meta_data b/obp-api/src/test/resources/frozen_type_meta_data index dd4635a28..3f6799424 100644 Binary files a/obp-api/src/test/resources/frozen_type_meta_data and b/obp-api/src/test/resources/frozen_type_meta_data differ diff --git a/obp-api/src/test/scala/code/api/v3_1_0/ConsentTest.scala b/obp-api/src/test/scala/code/api/v3_1_0/ConsentTest.scala index 7bdb23647..cc23dc8d9 100644 --- a/obp-api/src/test/scala/code/api/v3_1_0/ConsentTest.scala +++ b/obp-api/src/test/scala/code/api/v3_1_0/ConsentTest.scala @@ -103,19 +103,27 @@ class ConsentTest extends V310ServerSetup { } scenario("We will call the endpoint with user credentials", ApiEndpoint1, ApiEndpoint3, VersionOfApi, VersionOfApi2) { + setPropsValues("consumer_validation_method_for_consent"-> "CONSUMER_KEY_VALUE") wholeFunctionality(RequestHeader.`Consent-JWT`) + setPropsValues("consumer_validation_method_for_consent"-> "CONSUMER_CERTIFICATE") } scenario("We will call the endpoint with user credentials and deprecated header name", ApiEndpoint1, ApiEndpoint3, VersionOfApi, VersionOfApi2) { + setPropsValues("consumer_validation_method_for_consent"-> "CONSUMER_KEY_VALUE") wholeFunctionality(RequestHeader.`Consent-Id`) + setPropsValues("consumer_validation_method_for_consent"-> "CONSUMER_CERTIFICATE") } scenario("We will call the endpoint with user credentials-Implicit", ApiEndpoint1, ApiEndpoint3, VersionOfApi, VersionOfApi2) { + setPropsValues("consumer_validation_method_for_consent"-> "CONSUMER_KEY_VALUE") wholeFunctionalityImplicit(RequestHeader.`Consent-JWT`) + setPropsValues("consumer_validation_method_for_consent"-> "CONSUMER_CERTIFICATE") } scenario("We will call the endpoint with user credentials and deprecated header name-Implicit", ApiEndpoint1, ApiEndpoint3, VersionOfApi, VersionOfApi2) { + setPropsValues("consumer_validation_method_for_consent"-> "CONSUMER_KEY_VALUE") wholeFunctionalityImplicit(RequestHeader.`Consent-Id`) + setPropsValues("consumer_validation_method_for_consent"-> "CONSUMER_CERTIFICATE") } } diff --git a/obp-api/src/test/scala/code/api/v4_0_0/AccountBalanceTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/AccountBalanceTest.scala index 843335ec3..d11231ead 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/AccountBalanceTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/AccountBalanceTest.scala @@ -16,8 +16,8 @@ class AccountBalanceTest extends V400ServerSetup { * This is made possible by the scalatest maven plugin */ object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString) - object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.getBankAccountsBalances)) - object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getBankAccountBalances)) + object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.getBankAccountsBalancesForCurrentUser)) + object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getBankAccountBalancesForCurrentUser)) lazy val bankId = randomBankId lazy val bankAccount = randomPrivateAccountViaEndpoint(bankId) diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala index 98924bb88..f12d30b09 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/AccountAccessTest.scala @@ -5,10 +5,11 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.createViewJsonV300 import code.api.util.APIUtil.OAuth._ import code.api.util.ApiRole -import code.api.util.ErrorMessages.{UserLacksPermissionCanGrantAccessToCustomViewForTargetAccount, UserLacksPermissionCanGrantAccessToSystemViewForTargetAccount, UserLacksPermissionCanGrantAccessToViewForTargetAccount, UserLacksPermissionCanRevokeAccessToCustomViewForTargetAccount, UserLacksPermissionCanRevokeAccessToSystemViewForTargetAccount, UserLacksPermissionCanRevokeAccessToViewForTargetAccount, UserNotLoggedIn} +import code.api.util.ApiRole.CanSeeAccountAccessForAnyUser +import code.api.util.ErrorMessages._ import code.api.v3_0_0.ViewJsonV300 import code.api.v3_1_0.CreateAccountResponseJsonV310 -import code.api.v4_0_0.RevokedJsonV400 +import code.api.v4_0_0.{AccountsMinimalJson400, RevokedJsonV400} import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 import code.entitlement.Entitlement import com.github.dwickern.macros.NameOf.nameOf @@ -30,6 +31,7 @@ class AccountAccessTest extends V510ServerSetup { object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.grantUserAccessToViewById)) object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.revokeUserAccessToViewById)) object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.createUserWithAccountAccessById)) + object GetAccountAccessByUserId extends Tag(nameOf(Implementations5_1_0.getAccountAccessByUserId)) lazy val bankId = randomBankId @@ -54,6 +56,34 @@ class AccountAccessTest extends V510ServerSetup { createViewViaEndpoint(bankId, accountId, postBodyViewJson, user1) } + + + feature(s"test ${GetAccountAccessByUserId.name}") { + scenario(s"We will test ${GetAccountAccessByUserId.name}", GetAccountAccessByUserId, VersionOfApi) { + + val requestGet = (v5_1_0_Request / "users" / resourceUser2.userId / "account-access").GET + + // Anonymous call fails + val anonymousResponseGet = makeGetRequest(requestGet) + anonymousResponseGet.code should equal(401) + anonymousResponseGet.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + // Call endpoint without the entitlement + val badResponseGet = makeGetRequest(requestGet <@ user1) + badResponseGet.code should equal(403) + val errorMessage = badResponseGet.body.extract[ErrorMessage].message + errorMessage contains UserHasMissingRoles should be (true) + errorMessage contains CanSeeAccountAccessForAnyUser.toString() should be (true) + + // All good + Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanSeeAccountAccessForAnyUser.toString()) + val goodResponseGet = makeGetRequest(requestGet <@ user1) + goodResponseGet.code should equal(200) + goodResponseGet.body.extract[AccountsMinimalJson400] + + } + } + feature(s"test $ApiEndpoint1 Authorized access") { scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AccountBalanceTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AccountBalanceTest.scala new file mode 100644 index 000000000..5c102103f --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/AccountBalanceTest.scala @@ -0,0 +1,111 @@ +package code.api.v5_1_0 + +import code.api.util.APIUtil.OAuth._ +import code.api.util.ErrorMessages.UserNotLoggedIn +import code.api.v4_0_0.{AccountsBalancesJsonV400, BalanceJsonV400} +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import dispatch.Req +import net.liftweb.json +import org.scalatest.Tag + +class AccountBalanceTest extends V510ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.getBankAccountBalances)) + object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.getBankAccountsBalances)) + object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.getBankAccountsBalancesThroughView)) + + lazy val bankId = randomBankId + lazy val bankAccount = randomPrivateAccountViaEndpoint(bankId) + def requestGetAccountBalances(viewId: String = "None"): Req = (v5_1_0_Request / "banks" / bankAccount.bank_id / "accounts" / bankAccount.id / "views" / viewId / "balances").GET + def requestGetAccountsBalances(): Req = (v5_1_0_Request / "banks" / bankAccount.bank_id / "balances").GET + def requestGetAccountsBalancesThroughView(viewId: String = "None"): Req = (v5_1_0_Request / "banks" / bankAccount.bank_id / "views" / viewId / "balances").GET + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When(s"We make a request $ApiEndpoint1") + val responseGetAccountBalances = makeGetRequest(requestGetAccountBalances()) + Then("We should get a 401") + responseGetAccountBalances.code should equal(401) + responseGetAccountBalances.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access, no proper view") { + scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint1) { + val responseGetAccountBalances = makeGetRequest(requestGetAccountBalances() <@ user1) + Then("We should get a 403") + responseGetAccountBalances.code should equal(403) + } + } + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access with proper view") { + scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint1) { + val responseGetAccountBalances = makeGetRequest(requestGetAccountBalances("owner") <@ user1) + Then("We should get a 200") + responseGetAccountBalances.code should equal(200) + } + } + + + feature(s"test $ApiEndpoint2 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When(s"We make a request $ApiEndpoint1") + val responseGetAccountBalances = makeGetRequest(requestGetAccountsBalances()) + Then("We should get a 401") + responseGetAccountBalances.code should equal(401) + responseGetAccountBalances.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access with proper view") { + scenario("We will call the endpoint with user credentials", VersionOfApi, ApiEndpoint1) { + + val responseGetAccountBalances = makeGetRequest(requestGetAccountsBalances() <@ user1) + Then("We should get a 200") + responseGetAccountBalances.code should equal(200) + val accountsBefore = responseGetAccountBalances.body.extract[AccountsBalancesJsonV400].accounts.length + + // Make transaction + val amountOfMoney = "10.00" + // Create from and to account with 0 balance and transfer money + val (fromAccountId, toAccountId, _) = createTransactionRequest(bankId, amountOfMoney) + + val responseGetAccountBalances2 = makeGetRequest(requestGetAccountsBalances() <@ user1) + Then("We should get a 200") + responseGetAccountBalances2.code should equal(200) + + val accountsAfter = responseGetAccountBalances2.body.extract[AccountsBalancesJsonV400].accounts.length + accountsAfter should equal(accountsBefore + 2) + + val balances = responseGetAccountBalances2.body.extract[AccountsBalancesJsonV400] + + val toAccountBalance = balances.accounts.filter(_.account_id == toAccountId) + val filteredBalances: List[BalanceJsonV400] = toAccountBalance.flatMap(_.balances.filter(i => i.amount == amountOfMoney && i.currency == "EUR")) + filteredBalances.length should equal(1) + + val fromAccountBalance = balances.accounts.filter(_.account_id == fromAccountId) + val filteredFromAccountBalance: List[BalanceJsonV400] = fromAccountBalance.flatMap(_.balances.filter(i => i.amount == s"-${amountOfMoney}" && i.currency == "EUR")) + filteredFromAccountBalance.length should equal(1) + } + } + + + feature(s"test $ApiEndpoint3 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When(s"We make a request $ApiEndpoint1") + val responseGetAccountBalances = makeGetRequest(requestGetAccountsBalancesThroughView("owner")) + Then("We should get a 401") + responseGetAccountBalances.code should equal(401) + responseGetAccountBalances.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + + +} diff --git a/obp-api/src/test/scala/code/api/v5_1_0/AccountTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/AccountTest.scala index 131f61650..59cef9533 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/AccountTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/AccountTest.scala @@ -20,31 +20,18 @@ class AccountTest extends V510ServerSetup { * This is made possible by the scalatest maven plugin */ object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) - object GetAccountAccessByUserId extends Tag(nameOf(Implementations5_1_0.getAccountAccessByUserId)) + object GetCoreAccountByIdThroughView extends Tag(nameOf(Implementations5_1_0.getCoreAccountByIdThroughView)) - feature(s"test ${GetAccountAccessByUserId.name}") { - scenario(s"We will test ${GetAccountAccessByUserId.name}", GetAccountAccessByUserId, VersionOfApi) { + feature(s"test ${GetCoreAccountByIdThroughView.name}") { + scenario(s"We will test ${GetCoreAccountByIdThroughView.name}", GetCoreAccountByIdThroughView, VersionOfApi) { - val requestGet = (v5_1_0_Request / "users" / resourceUser2.userId / "account-access").GET + val requestGet = (v5_1_0_Request / "banks" / "BANK_ID" / "accounts" / "ACCOUNT_ID"/ "views" / "VIEW_ID").GET // Anonymous call fails val anonymousResponseGet = makeGetRequest(requestGet) anonymousResponseGet.code should equal(401) anonymousResponseGet.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) - // Call endpoint without the entitlement - val badResponseGet = makeGetRequest(requestGet <@ user1) - badResponseGet.code should equal(403) - val errorMessage = badResponseGet.body.extract[ErrorMessage].message - errorMessage contains UserHasMissingRoles should be (true) - errorMessage contains CanSeeAccountAccessForAnyUser.toString() should be (true) - - // All good - Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanSeeAccountAccessForAnyUser.toString()) - val goodResponseGet = makeGetRequest(requestGet <@ user1) - goodResponseGet.code should equal(200) - goodResponseGet.body.extract[AccountsMinimalJson400] - } } diff --git a/obp-api/src/test/scala/code/api/v5_1_0/CounterpartyLimitTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/CounterpartyLimitTest.scala new file mode 100644 index 000000000..3e9516539 --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/CounterpartyLimitTest.scala @@ -0,0 +1,185 @@ +package code.api.v5_1_0 +import java.util.UUID +import code.api.Constant.{SYSTEM_MANAGE_CUSTOM_VIEWS_VIEW_ID, SYSTEM_OWNER_VIEW_ID} +import code.api.util.ErrorMessages._ +import code.api.util.APIUtil.OAuth._ +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization.write +import org.scalatest.Tag + +class CounterpartyLimitTest extends V510ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.createCounterpartyLimit)) + object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.getCounterpartyLimit)) + object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.updateCounterpartyLimit)) + object ApiEndpoint4 extends Tag(nameOf(Implementations5_1_0.deleteCounterpartyLimit)) + + + val bankId = testBankId1.value + val accountId = testAccountId1.value + val ownerView = SYSTEM_OWNER_VIEW_ID + val postCounterpartyLimitV510 = code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.postCounterpartyLimitV510 + val putCounterpartyLimitV510 = PostCounterpartyLimitV510( + max_single_amount = 1, + max_monthly_amount = 2, + max_number_of_monthly_transactions = 3, + max_yearly_amount = 4, + max_number_of_yearly_transactions = 5 + ) + + + feature(s"test $ApiEndpoint1,$ApiEndpoint2, $ApiEndpoint3, $ApiEndpoint4, Authorized access") { + + scenario("We will call the endpoint without user credentials", ApiEndpoint1, ApiEndpoint2,ApiEndpoint3,ApiEndpoint4,VersionOfApi) { + val counterparty = createCounterparty(bankId, accountId, accountId, true, UUID.randomUUID.toString); + + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").POST + val response510 = makePostRequest(request510, write(postCounterpartyLimitV510)) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + { + + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").PUT + val response510 = makePutRequest(request510, write(postCounterpartyLimitV510)) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + } + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").GET + val response510 = makeGetRequest(request510) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + } + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").DELETE + val response510 = makeDeleteRequest(request510) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + } + } + + scenario("We will call the endpoint success case", ApiEndpoint1, ApiEndpoint2,ApiEndpoint3,ApiEndpoint4,VersionOfApi) { + val counterparty = createCounterparty(bankId, accountId, accountId, true, UUID.randomUUID.toString); + + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").POST <@ (user1) + val response510 = makePostRequest(request510, write(postCounterpartyLimitV510)) + Then("We should get a 201") + response510.code should equal(201) + response510.body.extract[CounterpartyLimitV510].max_monthly_amount should equal(postCounterpartyLimitV510.max_monthly_amount) + response510.body.extract[CounterpartyLimitV510].max_number_of_monthly_transactions should equal(postCounterpartyLimitV510.max_number_of_monthly_transactions) + response510.body.extract[CounterpartyLimitV510].max_number_of_yearly_transactions should equal(postCounterpartyLimitV510.max_number_of_yearly_transactions) + response510.body.extract[CounterpartyLimitV510].max_single_amount should equal(postCounterpartyLimitV510.max_single_amount) + response510.body.extract[CounterpartyLimitV510].max_yearly_amount should equal(postCounterpartyLimitV510.max_yearly_amount) + + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").GET<@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 200") + response510.code should equal(200) + response510.body.extract[CounterpartyLimitV510].max_monthly_amount should equal(postCounterpartyLimitV510.max_monthly_amount) + response510.body.extract[CounterpartyLimitV510].max_number_of_monthly_transactions should equal(postCounterpartyLimitV510.max_number_of_monthly_transactions) + response510.body.extract[CounterpartyLimitV510].max_number_of_yearly_transactions should equal(postCounterpartyLimitV510.max_number_of_yearly_transactions) + response510.body.extract[CounterpartyLimitV510].max_single_amount should equal(postCounterpartyLimitV510.max_single_amount) + response510.body.extract[CounterpartyLimitV510].max_yearly_amount should equal(postCounterpartyLimitV510.max_yearly_amount) + + } + + { + + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").PUT<@ (user1) + val response510 = makePutRequest(request510, write(putCounterpartyLimitV510)) + Then("We should get a 200") + response510.code should equal(200) + response510.body.extract[CounterpartyLimitV510].max_monthly_amount should equal(putCounterpartyLimitV510.max_monthly_amount) + response510.body.extract[CounterpartyLimitV510].max_number_of_monthly_transactions should equal(putCounterpartyLimitV510.max_number_of_monthly_transactions) + response510.body.extract[CounterpartyLimitV510].max_number_of_yearly_transactions should equal(putCounterpartyLimitV510.max_number_of_yearly_transactions) + response510.body.extract[CounterpartyLimitV510].max_single_amount should equal(putCounterpartyLimitV510.max_single_amount) + response510.body.extract[CounterpartyLimitV510].max_yearly_amount should equal(putCounterpartyLimitV510.max_yearly_amount) + } + + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").GET<@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 200") + response510.code should equal(200) + response510.body.extract[CounterpartyLimitV510].max_monthly_amount should equal(putCounterpartyLimitV510.max_monthly_amount) + response510.body.extract[CounterpartyLimitV510].max_number_of_monthly_transactions should equal(putCounterpartyLimitV510.max_number_of_monthly_transactions) + response510.body.extract[CounterpartyLimitV510].max_number_of_yearly_transactions should equal(putCounterpartyLimitV510.max_number_of_yearly_transactions) + response510.body.extract[CounterpartyLimitV510].max_single_amount should equal(putCounterpartyLimitV510.max_single_amount) + response510.body.extract[CounterpartyLimitV510].max_yearly_amount should equal(putCounterpartyLimitV510.max_yearly_amount) + } + + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").DELETE<@ (user1) + val response510 = makeDeleteRequest(request510) + Then("We should get a 204") + response510.code should equal(204) + } + } + + scenario("We will call the endpoint wrong bankId case", ApiEndpoint1, ApiEndpoint2,ApiEndpoint3,ApiEndpoint4,VersionOfApi) { + val counterparty = createCounterparty(bankId, accountId, accountId, true, UUID.randomUUID.toString); + + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "banks" / "wrongId" / "accounts" / accountId / "views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").POST <@ (user1) + val response510 = makePostRequest(request510, write(postCounterpartyLimitV510)) + Then("We should get a 404") + response510.code should equal(404) + response510.body.extract[ErrorMessage].message contains(BankNotFound) shouldBe (true) + + { + val request510 = (v5_1_0_Request / "banks" / "wrongId" / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").GET<@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 404") + response510.code should equal(404) + response510.body.extract[ErrorMessage].message contains(BankNotFound) shouldBe (true) + + } + + { + + val request510 = (v5_1_0_Request / "banks" / "wrongId" / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").PUT<@ (user1) + val response510 = makePutRequest(request510, write(putCounterpartyLimitV510)) + Then("We should get a 404") + response510.code should equal(404) + response510.body.extract[ErrorMessage].message contains(BankNotFound) shouldBe (true) + } + + { + val request510 = (v5_1_0_Request / "banks" / "wrongId" / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").GET<@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 404") + response510.code should equal(404) + response510.body.extract[ErrorMessage].message contains(BankNotFound) shouldBe (true) + } + + { + val request510 = (v5_1_0_Request / "banks" / "wrongId" / "accounts" / accountId /"views" / ownerView /"counterparties" / counterparty.counterpartyId /"limits").DELETE<@ (user1) + val response510 = makeDeleteRequest(request510) + Then("We should get a 404") + response510.code should equal(404) + response510.body.extract[ErrorMessage].message contains(BankNotFound) shouldBe (true) + } + } + } +} diff --git a/obp-api/src/test/scala/code/api/v5_1_0/CustomViewTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/CustomViewTest.scala new file mode 100644 index 000000000..2c03414fb --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/CustomViewTest.scala @@ -0,0 +1,231 @@ +package code.api.v5_1_0 + +import code.api.Constant.{SYSTEM_AUDITOR_VIEW_ID, SYSTEM_MANAGE_CUSTOM_VIEWS_VIEW_ID, SYSTEM_OWNER_VIEW_ID, SYSTEM_STAGE_ONE_VIEW_ID} +import code.api.util.APIUtil.OAuth._ +import code.api.util.ErrorMessages._ +import code.api.v5_1_0.OBPAPI5_1_0.Implementations5_1_0 +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization.write +import net.liftweb.util.StringHelpers +import org.scalatest.Tag + +import java.util.UUID + +class CustomViewTest extends V510ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations5_1_0.createCustomView)) + object ApiEndpoint2 extends Tag(nameOf(Implementations5_1_0.updateCustomView)) + object ApiEndpoint3 extends Tag(nameOf(Implementations5_1_0.getCustomView)) + object ApiEndpoint4 extends Tag(nameOf(Implementations5_1_0.deleteCustomView)) + + + val bankId = testBankId1.value + val accountId = testAccountId1.value + val ownerView = SYSTEM_OWNER_VIEW_ID + val manageCustomView = SYSTEM_MANAGE_CUSTOM_VIEWS_VIEW_ID + val targetViewId = "_test" + + val postCustomViewJsonWithFullPermissinos = code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.createCustomViewJson.copy(name=targetViewId) + val postCustomViewJson = code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.createCustomViewJson.copy( + name=targetViewId, + metadata_view=targetViewId, + allowed_permissions = List("can_see_transaction_start_date", "can_add_url") + ) + val putCustomViewJsonWithFullPermissinos = code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.updateCustomViewJson + val putCustomViewJson = code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.updateCustomViewJson.copy( + description = "1", + metadata_view = "2", + is_public = false, + which_alias_to_use = "",//TODO please check this field later. + hide_metadata_if_alias_used = false, + allowed_permissions = List("can_see_transaction_this_bank_account", "can_see_bank_account_owners") + ) + + feature(s"test Authorized access") { + + scenario(s"We will call the endpoint, $UserNotLoggedIn", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) { + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / ownerView /"target-views").POST + val response510 = makePostRequest(request510, write(postCustomViewJson)) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + { + + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"target-views" / targetViewId ).PUT + val response510 = makePutRequest(request510, write(putCustomViewJson)) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + } + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"target-views" / targetViewId ).GET + val response510 = makeGetRequest(request510) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + } + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"target-views" / targetViewId ).DELETE + val response510 = makeDeleteRequest(request510) + Then("We should get a 401") + response510.code should equal(401) + response510.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + + } + } + + scenario(s"We will call the endpoint, $SourceViewHasLessPermission", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) { + + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / ownerView /"target-views").POST <@ (user1) + val response510 = makePostRequest(request510, write(postCustomViewJsonWithFullPermissinos)) + Then("We should get a 400") + response510.code should equal(400) + response510.body.extract[ErrorMessage].message contains(SourceViewHasLessPermission) shouldBe(true) + + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"target-views" / targetViewId ).PUT <@ (user1) + val response510 = makePutRequest(request510, write(putCustomViewJsonWithFullPermissinos)) + Then("We should get a 400") + response510.code should equal(400) + response510.body.extract[ErrorMessage].message contains(SourceViewHasLessPermission) shouldBe(true) + } + } + + scenario(s"We will call the endpoint, $ViewDoesNotPermitAccess ", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) { + + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / ownerView /"target-views").POST <@ (user1) + val response510 = makePostRequest(request510, write(postCustomViewJson)) + Then("We should get a 400") + response510.code should equal(400) + response510.body.extract[ErrorMessage].message contains (ViewDoesNotPermitAccess) shouldBe (true) + response510.body.extract[ErrorMessage].message contains ("can_create_custom_view") shouldBe (true) + + { + + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"target-views" / targetViewId ).PUT <@ (user1) + val response510 = makePutRequest(request510, write(putCustomViewJson)) + Then("We should get a 400") + response510.code should equal(400) + response510.body.extract[ErrorMessage].message contains (ViewDoesNotPermitAccess) shouldBe (true) + response510.body.extract[ErrorMessage].message contains ("can_update_custom_view") shouldBe (true) + } + + { + When("we need to prepare the custom view for get and delete") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / manageCustomView / "target-views").POST <@ (user1) + val response510 = makePostRequest(request510, write(postCustomViewJson)) + Then("We should get a 201") + response510.code should equal(201) + } + { + + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / SYSTEM_AUDITOR_VIEW_ID /"target-views" / targetViewId ).GET <@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 400") + response510.code should equal(400) + response510.body.extract[ErrorMessage].message contains (ViewDoesNotPermitAccess) shouldBe (true) + response510.body.extract[ErrorMessage].message contains ("can_see_available_views_for_bank_account") shouldBe (true) + + } + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / ownerView / "target-views" / targetViewId).DELETE <@ (user1) + val response510 = makeDeleteRequest(request510) + Then("We should get a 400") + response510.code should equal(400) + response510.body.extract[ErrorMessage].message contains (ViewDoesNotPermitAccess) shouldBe (true) + response510.body.extract[ErrorMessage].message contains ("can_delete_custom_view") shouldBe (true) + + } + } + + scenario("We will call the endpoint with user credentials", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, ApiEndpoint4, VersionOfApi) { + + When("We make a request v5.1.0") + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / manageCustomView /"target-views").POST <@ (user1) + val response510 = makePostRequest(request510, write(postCustomViewJson)) + Then("We should get a 201") + response510.code should equal(201) + response510.body.extract[CustomViewJsonV510].name should equal(postCustomViewJson.name) + response510.body.extract[CustomViewJsonV510].description should equal(postCustomViewJson.description) + response510.body.extract[CustomViewJsonV510].metadata_view should equal(postCustomViewJson.metadata_view) + response510.body.extract[CustomViewJsonV510].is_public should equal(postCustomViewJson.is_public) + response510.body.extract[CustomViewJsonV510].alias should equal(postCustomViewJson.which_alias_to_use) + response510.body.extract[CustomViewJsonV510].hide_metadata_if_alias_used should equal(postCustomViewJson.hide_metadata_if_alias_used) + response510.body.extract[CustomViewJsonV510].allowed_permissions.sorted should equal(postCustomViewJson.allowed_permissions.sorted) + + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / ownerView / "target-views" / targetViewId).GET <@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 200") + response510.code should equal(200) + response510.body.extract[CustomViewJsonV510].name should equal(postCustomViewJson.name) + response510.body.extract[CustomViewJsonV510].description should equal(postCustomViewJson.description) + response510.body.extract[CustomViewJsonV510].metadata_view should equal(postCustomViewJson.metadata_view) + response510.body.extract[CustomViewJsonV510].is_public should equal(postCustomViewJson.is_public) + response510.body.extract[CustomViewJsonV510].alias should equal(postCustomViewJson.which_alias_to_use) + response510.body.extract[CustomViewJsonV510].hide_metadata_if_alias_used should equal(postCustomViewJson.hide_metadata_if_alias_used) + response510.body.extract[CustomViewJsonV510].allowed_permissions.sorted should equal(postCustomViewJson.allowed_permissions.sorted) + + } + + { + + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / manageCustomView /"target-views" / targetViewId ).PUT <@ (user1) + val response510 = makePutRequest(request510, write(putCustomViewJson)) + Then("We should get a 200") + response510.code should equal(200) + response510.body.extract[CustomViewJsonV510].description should equal(putCustomViewJson.description) + response510.body.extract[CustomViewJsonV510].metadata_view should equal(putCustomViewJson.metadata_view) + response510.body.extract[CustomViewJsonV510].is_public should equal(putCustomViewJson.is_public) + response510.body.extract[CustomViewJsonV510].alias should equal(putCustomViewJson.which_alias_to_use) + response510.body.extract[CustomViewJsonV510].hide_metadata_if_alias_used should equal(putCustomViewJson.hide_metadata_if_alias_used) + response510.body.extract[CustomViewJsonV510].allowed_permissions.sorted should equal(putCustomViewJson.allowed_permissions.sorted) + + } + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / ownerView /"target-views" / targetViewId ).GET <@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 200") + response510.code should equal(200) + response510.body.extract[CustomViewJsonV510].description should equal(putCustomViewJson.description) + response510.body.extract[CustomViewJsonV510].metadata_view should equal(putCustomViewJson.metadata_view) + response510.body.extract[CustomViewJsonV510].is_public should equal(putCustomViewJson.is_public) + response510.body.extract[CustomViewJsonV510].alias should equal(putCustomViewJson.which_alias_to_use) + response510.body.extract[CustomViewJsonV510].hide_metadata_if_alias_used should equal(putCustomViewJson.hide_metadata_if_alias_used) + response510.body.extract[CustomViewJsonV510].allowed_permissions.sorted should equal(putCustomViewJson.allowed_permissions.sorted) + + } + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId /"views" / manageCustomView /"target-views" / targetViewId ).DELETE <@ (user1) + val response510 = makeDeleteRequest(request510) + Then("We should get a 204") + response510.code should equal(204) + } + { + val request510 = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "views" / ownerView / "target-views" / targetViewId).GET <@ (user1) + val response510 = makeGetRequest(request510) + Then("We should get a 400") + response510.code should equal(400) + response510.body.extract[ErrorMessage].message contains (ViewNotFound) shouldBe(true) + + } + } + + } + +} diff --git a/obp-api/src/test/scala/code/api/v5_1_0/V510ServerSetup.scala b/obp-api/src/test/scala/code/api/v5_1_0/V510ServerSetup.scala index 0d9344715..fb612b1a5 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/V510ServerSetup.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/V510ServerSetup.scala @@ -2,22 +2,26 @@ package code.api.v5_1_0 import code.api.Constant.SYSTEM_OWNER_VIEW_ID import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.createViewJsonV300 import code.api.util.APIUtil.OAuth.{Consumer, Token, _} import code.api.util.ApiRole import code.api.util.ApiRole.CanCreateCustomer -import code.api.v1_2_1.{AccountJSON, AccountsJSON, ViewsJSONV121} -import code.api.v2_0_0.BasicAccountsJSON +import code.api.v1_2_1.{AccountJSON, AccountsJSON, PostTransactionCommentJSON, ViewsJSONV121} +import code.api.v1_4_0.JSONFactory1_4_0.TransactionRequestAccountJsonV140 +import code.api.v2_0_0.{BasicAccountsJSON, TransactionRequestBodyJsonV200} import code.api.v3_0_0.ViewJsonV300 -import code.api.v3_1_0.CustomerJsonV310 -import code.api.v4_0_0.{AtmJsonV400, BanksJson400} +import code.api.v3_1_0.{CreateAccountRequestJsonV310, CreateAccountResponseJsonV310, CustomerJsonV310} +import code.api.v4_0_0.{AtmJsonV400, BanksJson400, PostAccountAccessJsonV400, PostViewJsonV400, TransactionRequestWithChargeJSON400} import code.api.v5_0_0.PostCustomerJsonV500 import code.entitlement.Entitlement import code.setup.{APIResponse, DefaultUsers, ServerSetupWithTestData} -import com.openbankproject.commons.model.CreateViewJson +import com.openbankproject.commons.model.{AccountRoutingJsonV121, AmountOfMoneyJsonV121, CreateViewJson} import com.openbankproject.commons.util.ApiShortVersions import dispatch.Req import net.liftweb.json.Serialization.write +import net.liftweb.util.Helpers.randomString +import scala.util.Random import scala.util.Random.nextInt trait V510ServerSetup extends ServerSetupWithTestData with DefaultUsers { @@ -89,5 +93,88 @@ trait V510ServerSetup extends ServerSetupWithTestData with DefaultUsers { response.code should equal(201) response.body.extract[CustomerJsonV310] } + + def createTransactionRequestViaEndpoint(fromBankId: String, + fromAccountId: String, + fromCurrency: String, + fromViewId: String, + amount: String, + toBankId: String, + toAccountId: String, + consumerAndToken: Option[(Consumer, Token)]): TransactionRequestWithChargeJSON400 = { + val toAccountJson = TransactionRequestAccountJsonV140(toBankId, toAccountId) + val bodyValue = AmountOfMoneyJsonV121(fromCurrency, amount) + val description = "Just test it!" + val transactionRequestBody = TransactionRequestBodyJsonV200(toAccountJson, bodyValue, description) + val createTransReqRequest = (v5_1_0_Request / "banks" / fromBankId / "accounts" / fromAccountId / + fromViewId / "transaction-request-types" / "SANDBOX_TAN" / "transaction-requests").POST <@ (consumerAndToken) + + makePostRequest(createTransReqRequest, write(transactionRequestBody)).body.extract[TransactionRequestWithChargeJSON400] + } + def createAccountViaEndpoint(bankId : String, json: CreateAccountRequestJsonV310, consumerAndToken: Option[(Consumer, Token)]) = { + val entitlement = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, ApiRole.canCreateAccount.toString) + And("We make a request v4.0.0") + val request400 = (v5_1_0_Request / "banks" / bankId / "accounts" ).POST <@(consumerAndToken) + val response400 = makePostRequest(request400, write(json)) + + + Then("We should get a 201") + response400.code should equal(201) + val account = response400.body.extract[CreateAccountResponseJsonV310] + account.account_id should not be empty + Entitlement.entitlement.vend.deleteEntitlement(entitlement) + account + } + def grantUserAccessToViewViaEndpoint(bankId: String, + accountId: String, + userId: String, + consumerAndToken: Option[(Consumer, Token)], + postBody: PostViewJsonV400 + ): ViewJsonV300 = { + val postJson = PostAccountAccessJsonV400(userId, postBody) + val request = (v4_0_0_Request / "banks" / bankId / "accounts" / accountId / "account-access" / "grant").POST <@ (consumerAndToken) + val response = makePostRequest(request, write(postJson)) + Then("We should get a 201 and check the response body") + response.code should equal(201) + response.body.extract[ViewJsonV300] + } + def createTransactionRequest(bankId: String, amountOnMoney: String): (String, String, String) = { + // Create a Bank + val bank = createBank(bankId) + val addAccountJson1 = SwaggerDefinitionsJSON.createAccountRequestJsonV310 + .copy(user_id = resourceUser1.userId, balance = AmountOfMoneyJsonV121("EUR","0"), + account_routings = List(AccountRoutingJsonV121(Random.nextString(10), Random.nextString(10)))) + val addAccountJson2 = SwaggerDefinitionsJSON.createAccountRequestJsonV310 + .copy(user_id = resourceUser1.userId, balance = AmountOfMoneyJsonV121("EUR","0"), + account_routings = List(AccountRoutingJsonV121(Random.nextString(10), Random.nextString(10)))) + // Create from account + val fromAccount = createAccountViaEndpoint(bank.bankId.value, addAccountJson1, user1) + // Create to account + val toAccount = createAccountViaEndpoint(bank.bankId.value, addAccountJson2, user1) + // Create a custom view + val customViewJson = createViewJsonV300.copy(name = "_cascade_delete", metadata_view = "_cascade_delete", is_public = false).toCreateViewJson + val customView = createViewViaEndpoint(bank.bankId.value, fromAccount.account_id, customViewJson, user1) + // Grant access to the view + grantUserAccessToViewViaEndpoint( + bank.bankId.value, + fromAccount.account_id, + resourceUser1.userId, + user1, + PostViewJsonV400(view_id = customView.id, is_system = false) + ) + // Create a Transaction Request + val transactionRequest = createTransactionRequestViaEndpoint( + fromBankId = bank.bankId.value, + fromAccountId = fromAccount.account_id, + fromCurrency = fromAccount.balance.currency, + fromViewId = customView.id, + amount = amountOnMoney, + toBankId = bank.bankId.value, + toAccountId = toAccount.account_id, + user1 + ) + val transactionId = transactionRequest.transaction_ids.headOption.getOrElse("") + (fromAccount.account_id, toAccount.account_id, transactionId) + } } \ No newline at end of file diff --git a/obp-api/src/test/scala/code/connector/MockedCbsConnector.scala b/obp-api/src/test/scala/code/connector/MockedCbsConnector.scala index eb2d39ec4..24ca50053 100644 --- a/obp-api/src/test/scala/code/connector/MockedCbsConnector.scala +++ b/obp-api/src/test/scala/code/connector/MockedCbsConnector.scala @@ -1,7 +1,6 @@ package code.connector -import code.api.Constant -import code.api.Constant.SYSTEM_STANDARD_VIEW_ID +import code.api.Constant._ import code.api.util.{CallContext, CustomJsonFormats} import code.bankconnectors._ import code.setup.{DefaultConnectorTestSetup, DefaultUsers, ServerSetup} @@ -39,7 +38,7 @@ object MockedCbsConnector extends ServerSetup balanceAmount = "", balanceCurrency = "", owners = List(""), - viewsToGenerate = SYSTEM_STANDARD_VIEW_ID :: Constant.SYSTEM_OWNER_VIEW_ID :: "_Public" :: "Accountant" :: "Auditor" :: Nil, + viewsToGenerate = SYSTEM_STANDARD_VIEW_ID :: SYSTEM_OWNER_VIEW_ID :: CUSTOM_PUBLIC_VIEW_ID :: SYSTEM_ACCOUNTANT_VIEW_ID :: SYSTEM_AUDITOR_VIEW_ID :: Nil, bankRoutingScheme = "", bankRoutingAddress = "", branchRoutingScheme = "", @@ -55,7 +54,7 @@ object MockedCbsConnector extends ServerSetup balanceAmount = "", balanceCurrency = "", owners = List(""), - viewsToGenerate = SYSTEM_STANDARD_VIEW_ID :: Constant.SYSTEM_OWNER_VIEW_ID :: "_Public" :: "Accountant" :: "Auditor" :: Nil, + viewsToGenerate = SYSTEM_STANDARD_VIEW_ID :: SYSTEM_OWNER_VIEW_ID :: CUSTOM_PUBLIC_VIEW_ID :: SYSTEM_ACCOUNTANT_VIEW_ID :: SYSTEM_AUDITOR_VIEW_ID :: Nil, bankRoutingScheme = "", bankRoutingAddress = "", branchRoutingScheme = "", diff --git a/obp-api/src/test/scala/code/util/APIUtilHeavyTest.scala b/obp-api/src/test/scala/code/util/APIUtilHeavyTest.scala index 798c75542..ade584eb3 100644 --- a/obp-api/src/test/scala/code/util/APIUtilHeavyTest.scala +++ b/obp-api/src/test/scala/code/util/APIUtilHeavyTest.scala @@ -27,6 +27,7 @@ TESOBE (http://www.tesobe.com/) package code.util +import code.api.Constant.SYSTEM_OWNER_VIEW_ID import code.api.UKOpenBanking.v2_0_0.{APIMethods_UKOpenBanking_200, OBP_UKOpenBanking_200} import code.api.UKOpenBanking.v3_1_0.{APIMethods_AccountAccessApi, OBP_UKOpenBanking_310} import code.api.berlin.group.v1_3.OBP_BERLIN_GROUP_1_3 @@ -37,6 +38,7 @@ import code.api.v3_1_0.OBPAPI3_1_0 import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 import code.api.v4_0_0.{OBPAPI4_0_0, V400ServerSetup} import code.setup.PropsReset +import code.views.system.ViewDefinition import com.openbankproject.commons.util.ApiVersion class APIUtilHeavyTest extends V400ServerSetup with PropsReset { @@ -164,5 +166,30 @@ class APIUtilHeavyTest extends V400ServerSetup with PropsReset { allowedOperationIds5 contains("UKv3.1-createAccountAccessConsents") should be (true) allowedOperationIds5 contains("UKv3.1-deleteConsent") should be (false) } + + feature("test APIUtil.getPermissionPairFromViewDefinition method") { + + scenario(s"Test the getPermissionPairFromViewDefinition method") { + + val subList = List( + "can_see_transaction_request_types", + "can_see_available_views_for_bank_account", + "can_see_views_with_permissions_for_all_users", + "can_see_views_with_permissions_for_one_user", + "can_see_transaction_this_bank_account", + "can_see_transaction_other_bank_account", + "can_see_transaction_description", + "can_see_transaction_start_date", + "can_see_transaction_finish_date", + "can_see_bank_account_national_identifier", + "can_see_bank_account_swift_bic" + ).toSet + val systemOwnerView = getOrCreateSystemView(SYSTEM_OWNER_VIEW_ID) + val permissions = APIUtil.getViewPermissions(systemOwnerView.asInstanceOf[ViewDefinition]) + + subList.subsetOf(permissions) + } + + } } \ No newline at end of file diff --git a/obp-api/src/test/scala/code/views/MappedViewsTest.scala b/obp-api/src/test/scala/code/views/MappedViewsTest.scala index 16b8a6b98..55f273677 100644 --- a/obp-api/src/test/scala/code/views/MappedViewsTest.scala +++ b/obp-api/src/test/scala/code/views/MappedViewsTest.scala @@ -60,7 +60,9 @@ class MappedViewsTest extends ServerSetup with DefaultUsers{ val wrongViewId = "WrongViewId" val wrongView = MapperViews.getOrCreateSystemViewFromCbs(wrongViewId) - wrongView should equal(Failure(ViewIdNotSupported+ s"Your input viewId is :$wrongViewId")) + wrongView.toString contains ViewIdNotSupported shouldBe (true) + + wrongView.toString contains wrongViewId shouldBe(true) } diff --git a/obp-commons/pom.xml b/obp-commons/pom.xml index 6f28d7323..c7f68bad4 100644 --- a/obp-commons/pom.xml +++ b/obp-commons/pom.xml @@ -75,7 +75,7 @@ com.google.guava guava - 31.1-jre + 32.0.0-jre diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala index 45147f229..256ba39ec 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/ViewModel.scala @@ -57,7 +57,7 @@ trait ViewSpecification { } /* -The JSON that should be supplied to create a view. Conforms to ViewSpecification +The JSON that should be supplied to create a custom view. Conforms to ViewSpecification */ case class CreateViewJson( name: String, @@ -73,7 +73,7 @@ case class CreateViewJson( /* -The JSON that should be supplied to update a view. Conforms to ViewSpecification +The JSON that should be supplied to update a system view. Conforms to ViewSpecification */ case class UpdateViewJSON( description: String,