diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 51581007..134891fe 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,5 @@ +--- + version: 2 updates: - package-ecosystem: npm diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 13cf05fb..3eb65f7c 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -1,3 +1,4 @@ +--- # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven @@ -5,46 +6,45 @@ name: Java CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v3 - with: - node-version: 18 - - name: Yarn Audit client - run: yarn audit --level high --groups dependencies - working-directory: client - - name: Yarn Audit welcome - run: yarn audit --level high --groups dependencies - working-directory: welcome - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - java-version: '17' - distribution: 'adopt' - cache: 'maven' - - name: Set up MySQL - uses: shogo82148/actions-setup-mysql@v1 - with: - mysql-version: '8.0' - - run: | - mysql -uroot -h127.0.0.1 -e \ - "CREATE DATABASE access CHARACTER SET utf8mb4 \ - COLLATE utf8mb4_0900_ai_ci;" - - run: | - mysql -uroot -h127.0.0.1 -e \ - "CREATE USER 'access'@'localhost' IDENTIFIED BY 'secret';"; - - run: | - mysql -uroot -h127.0.0.1 -e \ - "GRANT ALL privileges ON access.* TO 'access'@'localhost';" - - name: Build with Maven - run: mvn clean install --file pom.xml - - name: Codecov - uses: codecov/codecov-action@v1.3.1 + - uses: actions/checkout@v2 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Yarn Audit client + run: yarn audit --level high --groups dependencies + working-directory: client + - name: Yarn Audit welcome + run: yarn audit --level high --groups dependencies + working-directory: welcome + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'adopt' + cache: 'maven' + - name: Set up MySQL + uses: shogo82148/actions-setup-mysql@v1 + with: + mysql-version: '8.0' + - run: | + mysql -uroot -h127.0.0.1 -e \ + "CREATE DATABASE access CHARACTER SET utf8mb4 \ + COLLATE utf8mb4_0900_ai_ci;" + - run: | + mysql -uroot -h127.0.0.1 -e \ + "CREATE USER 'access'@'localhost' IDENTIFIED BY 'secret';"; + - run: | + mysql -uroot -h127.0.0.1 -e \ + "GRANT ALL privileges ON access.* TO 'access'@'localhost';" + - name: Build with Maven + run: mvn clean install --file pom.xml + - name: Codecov + uses: codecov/codecov-action@v1.3.1 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ca9515d6..c82c336f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,3 +1,5 @@ +--- + name: Maven install on: @@ -14,111 +16,111 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Setup Maven - uses: stCarolas/setup-maven@v.4.5 - with: - maven-version: 3.8.7 - - - name: Set up cache - uses: actions/cache@v1 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - - name: Determine the version - run: echo ::set-output name=version::$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec) - id: versioncheck - - - name: Exit when workflow_dispatch is triggered, and the version does not contain SNAPSHOT in it's name - run: | - echo "Only SNAPSHOT releases can be triggered with the workflow_dispatch" - exit 1 - if: github.event_name == 'workflow_dispatch' && ( !endsWith(steps.versioncheck.outputs.version, '-SNAPSHOT')) - - - name: Exit when a production build is triggered, and the github tag is not the same as the version in pom.xml - run: | - echo echo "Project version ${{ steps.versioncheck.outputs.version }} does not match git tag ${{ github.ref_name }}" - exit 1 - if: github.event_name != 'workflow_dispatch' && steps.versioncheck.outputs.version != github.ref_name - - - name: Set up JDK 17 for snapshots - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - server-id: openconext-snapshots - server-username: MAVEN_USERNAME - server-password: MAVEN_PASSWORD - if: ( endsWith(steps.versioncheck.outputs.version, '-SNAPSHOT')) - - - name: Set up JDK 17 for releases - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - server-id: openconext-releases - server-username: MAVEN_USERNAME - server-password: MAVEN_PASSWORD - if: ${{!( endsWith(steps.versioncheck.outputs.version, '-SNAPSHOT')) }} - - - name: Deploy with Maven - run: mvn --batch-mode deploy -DskipTests - env: - MAVEN_USERNAME: ${{ secrets.BUILD_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.BUILD_PASSWORD }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push the server image - uses: docker/build-push-action@v4 - with: - context: server - file: server/docker/Dockerfile - platforms: linux/amd64 - push: true - tags: | - ghcr.io/openconext/openconext-invite/inviteserver:${{ steps.versioncheck.outputs.version }} - - - name: Build and push the client image - uses: docker/build-push-action@v4 - with: - context: client - file: client/docker/Dockerfile - platforms: linux/amd64 - push: true - tags: | - ghcr.io/openconext/openconext-invite/inviteclient:${{ steps.versioncheck.outputs.version }} - - - name: Build and push the welcome image - uses: docker/build-push-action@v4 - with: - context: welcome - file: welcome/docker/Dockerfile - platforms: linux/amd64 - push: true - tags: | - ghcr.io/openconext/openconext-invite/invitewelcome:${{ steps.versioncheck.outputs.version }} - - - name: Build and push the provisioning mock image - uses: docker/build-push-action@v4 - with: - context: provisioning-mock - file: provisioning-mock/docker/Dockerfile - platforms: linux/amd64 - push: true - tags: | - ghcr.io/openconext/openconext-invite/inviteprovisioningmock:${{ steps.versioncheck.outputs.version }} + - uses: actions/checkout@v2 + + - name: Setup Maven + uses: stCarolas/setup-maven@v.4.5 + with: + maven-version: 3.8.7 + + - name: Set up cache + uses: actions/cache@v1 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: Determine the version + run: echo ::set-output name=version::$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec) + id: versioncheck + + - name: Exit when workflow_dispatch is triggered, and the version does not contain SNAPSHOT in it's name + run: | + echo "Only SNAPSHOT releases can be triggered with the workflow_dispatch" + exit 1 + if: github.event_name == 'workflow_dispatch' && ( !endsWith(steps.versioncheck.outputs.version, '-SNAPSHOT')) + + - name: Exit when a production build is triggered, and the github tag is not the same as the version in pom.xml + run: | + echo echo "Project version ${{ steps.versioncheck.outputs.version }} does not match git tag ${{ github.ref_name }}" + exit 1 + if: github.event_name != 'workflow_dispatch' && steps.versioncheck.outputs.version != github.ref_name + + - name: Set up JDK 17 for snapshots + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: 'maven' + server-id: openconext-snapshots + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + if: ( endsWith(steps.versioncheck.outputs.version, '-SNAPSHOT')) + + - name: Set up JDK 17 for releases + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: 'maven' + server-id: openconext-releases + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + if: ${{!( endsWith(steps.versioncheck.outputs.version, '-SNAPSHOT')) }} + + - name: Deploy with Maven + run: mvn --batch-mode deploy -DskipTests + env: + MAVEN_USERNAME: ${{ secrets.BUILD_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.BUILD_PASSWORD }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push the server image + uses: docker/build-push-action@v4 + with: + context: server + file: server/docker/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ghcr.io/openconext/openconext-invite/inviteserver:${{ steps.versioncheck.outputs.version }} + + - name: Build and push the client image + uses: docker/build-push-action@v4 + with: + context: client + file: client/docker/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ghcr.io/openconext/openconext-invite/inviteclient:${{ steps.versioncheck.outputs.version }} + + - name: Build and push the welcome image + uses: docker/build-push-action@v4 + with: + context: welcome + file: welcome/docker/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ghcr.io/openconext/openconext-invite/invitewelcome:${{ steps.versioncheck.outputs.version }} + + - name: Build and push the provisioning mock image + uses: docker/build-push-action@v4 + with: + context: provisioning-mock + file: provisioning-mock/docker/Dockerfile + platforms: linux/amd64 + push: true + tags: | + ghcr.io/openconext/openconext-invite/inviteprovisioningmock:${{ steps.versioncheck.outputs.version }} diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml new file mode 100644 index 00000000..cb221a62 --- /dev/null +++ b/.github/workflows/doc.yml @@ -0,0 +1,67 @@ +--- +name: make-doc + +# Triggers the workflow on push or pull request events +on: [push, pull_request] # yamllint disable-line + +jobs: + test-redoc: + runs-on: ubuntu-latest + name: Test documentation and generate openapi html documentation + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: lint markdown files + uses: nosborn/github-action-markdown-cli@v2.0.0 + with: + files: . + + - name: lint yaml files + uses: ibiqlik/action-yamllint@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + java-version: '17' + distribution: 'adopt' + cache: 'maven' + - name: Set up MySQL + uses: shogo82148/actions-setup-mysql@v1 + with: + mysql-version: '8.0' + - run: | + mysql -uroot -h127.0.0.1 -e \ + "CREATE DATABASE access CHARACTER SET utf8mb4 \ + COLLATE utf8mb4_0900_ai_ci;" + - run: | + mysql -uroot -h127.0.0.1 -e \ + "CREATE USER 'access'@'localhost' IDENTIFIED BY 'secret';"; + - run: | + mysql -uroot -h127.0.0.1 -e \ + "GRANT ALL privileges ON access.* TO 'access'@'localhost';" + - name: Generate openapi.json + run: mvn verify -DskipTests=true + + - name: Test api-specs with redoc-cli + uses: seeebiii/redoc-cli-github-action@v10 + with: + args: 'bundle server/target/openapi.json -t template.hbs -o docs/api/index.html' + + - name: check result + run: | + ls -al docs/api/ + test -f docs/api/index.html || (echo "Missing docs/index.html from previous step." && exit 1) + + - name: Commit files if the html has changed + run: | + git config user.name github-actions + git config user.email github-actions@github.com + git add docs/api/index.html + git diff-index --quiet HEAD || git commit -m "Update github page" + + - name: Push changes + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref }} diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..60b9d883 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,18 @@ +--- +# MD013/line-length - Line length +MD013: + # Number of characters + line_length: 160 + # Number of characters for headings + heading_line_length: 80 + # Number of characters for code blocks + code_block_line_length: 80 + # Include code blocks + code_blocks: false + # Include tables + tables: false + +# MD033/no-inline-html - Inline HTML +MD033: + # Allowed elements + allowed_elements: [br] diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000..241c55c1 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +*/node_modules/ \ No newline at end of file diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 00000000..5590e131 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,13 @@ +--- +extends: default + +ignore: + - '*/node_modules/' + - '*/target/' + + +rules: + # 80 chars should be enough, but don't fail if a line is longer + line-length: + max: 160 + level: warning diff --git a/.yamllintignore b/.yamllintignore new file mode 100644 index 00000000..241c55c1 --- /dev/null +++ b/.yamllintignore @@ -0,0 +1 @@ +*/node_modules/ \ No newline at end of file diff --git a/README.md b/README.md index 6ac6a59f..3320e890 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # Openconext-Invite -# invite-server [![Build Status](https://github.com/OpenConext/OpenConext-Access/actions/workflows/actions.yml/badge.svg)](https://github.com/SOpenConext/OpenConext-Access/actions/workflows/actions.yml/badge.svg) [![codecov](https://codecov.io/gh/OpenConext/OpenConext-Access/branch/main/graph/badge.svg?token=HZ7ES3TLQ9)](https://codecov.io/gh/OpenConext/OpenConext-Access) @@ -41,37 +40,42 @@ To build and deploy (the latter requires credentials in your maven settings): mvn clean deploy ``` -### [Endpoints](#endpoint) +### [Endpoints](#endpoints) -https://access.test2.surfconext.nl/ui/swagger-ui/index.html + -https://mock.test2.surfconext.nl/ + -https://welcome.test2.surfconext.nl/ + -https://access.test2.surfconext.nl/ + ### [Mock](#mock) If you want to use the mock-provisioning, add the following metadata in Manage. SCIM: -``` + +```json "provisioning_type": "scim", "scim_url": "https://mock.test2.surfconext.nl/api/scim/v2", "scim_user": "user", "scim_password": "secret", "scim_update_role_put_method": true ``` + eVA -``` + +```json "provisioning_type": "eva", "eva_token": "secret", "eva_guest_account_duration": 30 "eva_url": "https://mock.test2.surfconext.nl/eva", ``` + Graph -``` + +```json "provisioning_type": "graph", "graph_url": "https://mock.test2.surfconext.nl/graph/users", "graph_client_id" : "client_id", @@ -80,20 +84,21 @@ Graph "graph_tenant": "tenant" ``` -### [Local endpoints](#local-endpoint) +### [Local endpoints](#local-endpoints) Login with Mujina IdP and user `admin` to become super-user in the local environment -http://localhost:8080/ui/swagger-ui/index.html + -http://localhost:8081/ + -http://localhost:4000 + -http://localhost:3000 + ### [Institution Admin](#institution-admin) To become an institution admin in invite, add the following values as `eduPersonEntitlements` using Mujina: -* urn:mace:surfnet.nl:surfnet.nl:sab:organizationGUID:ad93daef-0911-e511-80d0-005056956c1a -* urn:mace:surfnet.nl:surfnet.nl:sab:role:SURFconextverantwoordelijke \ No newline at end of file + +- urn:mace:surfnet.nl:surfnet.nl:sab:organizationGUID:ad93daef-0911-e511-80d0-005056956c1a +- urn:mace:surfnet.nl:surfnet.nl:sab:role:SURFconextverantwoordelijke diff --git a/docs/SCIM/index.md b/docs/SCIM/index.md new file mode 100644 index 00000000..836e3877 --- /dev/null +++ b/docs/SCIM/index.md @@ -0,0 +1,392 @@ + +# SCIM - System for Cross-domain Identity Management + +De relevante gebruikers en hun groepslidmaatschappen worden via het SCIM protocol doorgegeven aan de endpoints bij instellingen. +De invite-applicatie heeft de rol van SCIM client om informatie aan de verschillende Service Providers te sturen. + +## Begrippen + +| | | +|---|---| +| Service Provider | Een applicatie bij de instelling, waar een gast-gebruiker toegang moet krijgen | +| SCIM client | De applicatie die de gebruikersinformatie naar de Serice Providers stuurt; De Invite-applicatie backend | + +## Akties + +De endpoints bij de instellingen ondersteunen de volgende operaties: + +- Create: POST `https://example.com/{v}/{resource}` +- Read: GET `https://example.com/{v}/{resource}/{id}` +- Replace: PUT `https://example.com/{v}/{resource}/{id}` +- Delete: DELETE `https://example.com/{v}/{resource}/{id}` +- Update: PATCH `https://example.com/{v}/{resource}/{id}` +- Search: GET `https://example.com/{v}/{resource}?filter={attribute}{op}{value}&sortBy={attributeName}&sortOrder={ascending|descending}` + +PUT operaties leveren het complete object; PATCH operaties geven het verschil met het huidige object door. [Zie rfc7644 section-3.5.2](https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2) + +## Identifiers + +Er zijn meerdere attributen die een gebruiker of groep identificeren: + +- **id** : De identifier voor een gebruiker of groep bij de Service Provider +- **externalId** : De identifier binnen de gastenapplicatie +- **userName** : De identifier voor een gebruiker bij de Service Provider, +in het SCIM protocol de inlognaam voor de gebruiker als deze bij de Service Provider in gaat loggen. + +Voor de gebruikers die via de invite-applicatie beheerd worden, gebruiken we de eduPersonPrincipalName (eppn) voor de indentifiers, +zodat ze ook bij een SAML of oidc authenticatie herkend kunnen worden. + +## Gebruikers + +### Aanmaken gebruiker + +Na het accepteren van de eerste uitnodiging van een instelling, moet de gebruiker aangemaakt worden bij de instelling. + +#### Request + +```curl +POST /v1/Users HTTP/1.1 +Accept: application/json +Authorization: Basic dXNlcjpwYXNzd29yZA== +Host: example.com +Content-Length: ... +Content-Type: application/json +{ + "schemas":["urn:ietf:params:scim:schemas:core:2.0:User"], + "externalId":"c2cd7d6e-63fc-493a-8746-62fb2d3f8806@eduid.nl", + "userName":"c2cd7d6e-63fc-493a-8746-62fb2d3f8806@eduid.nl", + "name":{ + "familyName":"Havekes", + "givenName":"Peter" + }, + "displayName": "Peter Havekes", + "emails":[ + { + "type":"other", + "value":"peter@gmnail.com" + } + ] +} +``` + +#### Response + +```curl +HTTP/1.1 201 Created +Content-Type: application/scim+json +Location: https://example.com/v1/Users/{UserID at SP} +{ + "schemas": [ + "urn:ietf:params:scim:schemas:core:2.0:User", + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" + ], + "displayName": "Peter Havekes", + "meta": { + "created": "2021-12-22T12:34:56Z", + "location": "https://example.com/v1/Users/{UserID at SP}", + "lastModified": "2021-12-22T12:34:56Z", + "resourceType": "User" + }, + "name":{ + "familyName":"Havekes", + "givenName":"Peter" + }, + "id": "{UserID at SP}", + "userName":"c2cd7d6e-63fc-493a-8746-62fb2d3f8806@eduid.nl", + "emails":[ + { + "type":"other", + "value":"peter@gmnail.com" + } + ] +} +``` + +De **{UserID at SP}** uit het antwoord wordt in de Invite-applicatie opgeslagen bij de user, voor toekomstige updates van de gebruiker. + +### Update gebruiker + +Als de gegevens van een gebruiker veranderd zijn (veranderde attributen tijdens de authenticatie), +dan sturen we een geupdate user-object naar alle service providers waar deze gebruiker bekend is. + +#### Request + +```curl +PUT /v1/users/{UserID at SP} HTTP/1.1 +Accept: application/json +Authorization: Basic dXNlcjpwYXNzd29yZA== +Host: example.com +Content-Length: ... +Content-Type: application/json +{ + "schemas":["urn:ietf:params:scim:schemas:core:2.0:User"], + "externalId":"c2cd7d6e-63fc-493a-8746-62fb2d3f8806@eduid.nl", + "userName":"c2cd7d6e-63fc-493a-8746-62fb2d3f8806@eduid.nl", + "name":{ + "familyName":"Havekes-Nieuwenaam", + "givenName":"Peter" + }, + "id": "{UserID at SP}", + "displayName": "Peter Havekes-Nieuwenaam", + "emails":[ + { + "type":"other", + "value":"peter@gmnail.com" + } + ] +} +``` + +#### Response + +```curl +HTTP/1.1 200 OK +Content-Type: application/scim+json +Location: https://example.com/v1/Users/{UserID at SP} +{ + "schemas": [ + "urn:ietf:params:scim:schemas:core:2.0:User", + "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User" + ], + "displayName": "Peter Havekes-Nieuwenaam", + "name":{ + "familyName":"Havekes-Nieuwenaam", + "givenName":"Peter" + }, + "meta": { + "created": "2021-12-22T12:34:56Z", + "location": "https://example.com/v1/Users/{UserID at SP}", + "lastModified": "2021-12-22T20:34:56Z", + "resourceType": "User" + }, + "id": "{UserID at SP}", + "userName":"c2cd7d6e-63fc-493a-8746-62fb2d3f8806@eduid.nl", + "emails":[ + { + "type":"other", + "value":"peter@gmail.com" + } + ] +} +``` + +### Verwijder gebruiker + +Niet actieve gebruikers worden na X dagen verwijderd uit de invite applicatie, en ook bij alle service providers waar de gebruiker is aangemaakt. + +#### Request + +```curl +DELETE /users/{UserID at SP} HTTP/1.1 +Accept: application/json +Authorization: Basic dXNlcjpwYXNzd29yZA== +Host: example.com +``` + +#### Response + +```curl +HTTP/1.1 200 OK +``` + +## Groepen + +De rollen in de invite applicatie worden als rollen gepubliceerd naar de Service Provider. + +### Aanmaken groep + +Bij het aanmaken van een groep in de invite applicatie wordt deze direct verstuurd naar de instelling. + +#### Request + +```curl +POST /v1/Groups HTTP/1.1 +Accept: application/json +Authorization: Basic dXNlcjpwYXNzd29yZA== +Host: example.com +Content-Length: ... +Content-Type: application/json +{ + "schemas": + [ + "urn:ietf:params:scim:schemas:core:2.0:Group" + ], + "externalId": "urn:collab:group:test.eduid.nl:wur.nl:brightspace:gastdocent", + "displayName":"WUR Brightspace gastdocent", + "members": + [ + { + "value":"{UserID at SP}" + } + ] +} +``` + +Bij het aanmaken van een groep zal de lijst met `members` leeg zijn. + +#### Response + +```curl +HTTP/1.1 201 Created +Content-Type: application/json +Location: https://example.com/v1/Groups/{GroupID at SP} +{ + "schemas": [ + "urn:ietf:params:scim:schemas:core:2.0:Group" + ], + "displayName":"WUR Brightspace gastdocent", + "meta": { + "created": "2021-12-23T10:00:00Z", + "location": "https://example.com/v1/Groups/{GroupID at SP}", + "lastModified": "2021-12-23T10:00:00Z", + "resourceType": "Group" + }, + "members": + [ + { + "value":"{UserID at SP}" + } + ] + "externalId": "urn:collab:group:test.eduid.nl:wur.nl:brightspace:gastdocent", + "id": "{GroupID at SP}" +} +``` + +De **{GroupID at SP}** uit het antwoord wordt in de Invite-applicatie opgeslagen bij de user, voor toekomstige updates van de groep. + +### Update groep (Gebruiker toevoegen/verwijderen) + +Als een gebruiker een uitnodiging accepteert, wordt de gebruiker eerst aangemaakt (met bovenstaand user bericht) als deze nog niet bestond. +Daarna wordt de gebruiker aan de bestaande groep toegevoegd door het hele groep-object (met alle leden) als update te sturen (PUT) +of door het verschil door te geven (PATCH). + +Per applicatie is in te stellen of groep-updates als PUT of PATCH verstuurd worden: + +PUT operaties leveren het complete object; PATCH operaties geven het verschil met het huidige object door. [Zie rfc7644 section-3.5.2](https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2) + +#### Request PUT + +```curl +PUT /v1/Groups/{GroupID at SP} HTTP/1.1 +Accept: application/json +Authorization: Basic dXNlcjpwYXNzd29yZA== +Host: example.com +Content-Length: ... +Content-Type: application/json +{ + "schemas": + [ + "urn:ietf:params:scim:schemas:core:2.0:Group" + ], + "externalId": "urn:collab:group:test.eduid.nl:wur.nl:brightspace:gastdocent", + "id": "{GroupID at SP}" + "displayName":"WUR Brightspace gastdocent", + "members": + [ + { + "value":"{UserID at SP}", + "externalId": "{Internal UserID at Invite-application}" + }, + { + "value":"{Other UserID at SP}", + "externalId": "{Other Internal UserID at Invite-application}" + } + ] +} +``` + +#### Request PATCH + +```curl +PATCH /v1/Groups/{GroupID at SP} HTTP/1.1 +Accept: application/json +Authorization: Basic dXNlcjpwYXNzd29yZA== +Host: example.com +Content-Length: ... +Content-Type: application/json +{ + "schemas" : [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ], + "externalId" : "test.eduid.nl.uva.canvas.guest", + "id" : "{GroupID at SP}", + "Operations" : [ { + "op" : "Add", + "path" : "members", + "value" : [ { + "value" : "{UserID at SP}" + } ] + } ] +} +``` + +```curl +PATCH /v1/Groups/{GroupID at SP} HTTP/1.1 +Accept: application/json +Authorization: Basic dXNlcjpwYXNzd29yZA== +Host: example.com +Content-Length: ... +Content-Type: application/json +{ + "schemas" : [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ], + "externalId" : "test.eduid.nl.uva.canvas.guest", + "id" : "{GroupID at SP}", + "Operations" : [ { + "op" : "Remove", + "path" : "members", + "value" : [ { + "value" : "{UserID at SP}" + } ] + } ] +} +``` + +#### Response + +```curl +HTTP/1.1 200 OK +Content-Type: application/json +Location: https://example.com/v1/Groups/{GroupID at SP} +{ + "schemas": [ + "urn:ietf:params:scim:schemas:core:2.0:Group" + ], + "displayName":"WUR Brightspace gastdocent", + "meta": { + "created": "2021-12-23T10:00:00Z", + "location": "https://example.com/v1/Groups/{GroupID at SP}", + "lastModified": "2021-12-23T14:01:00Z", + "resourceType": "Group" + }, + "members": + [ + { + "value":"{UserID at SP}", + "externalId": "{Internal UserID at Invite-application}" + }, + { + "value":"{Other UserID at SP}", + "externalId": "{Other Internal UserID at Invite-application}" + } + ] + "externalId": "urn:collab:group:test.eduid.nl:wur.nl:brightspace:gastdocent", + "id": "{GroupID at SP}" +} +``` + +### Verwijder groep + +Als groepen worden verwijderd vanuit de invite applicatie wordt dit ook doorgegeven aan de service provider. + +#### Request + +```curl +DELETE /groups/{GroupID at SP} HTTP/1.1 +Accept: application/json +Authorization: Basic dXNlcjpwYXNzd29yZA== +Host: example.com +``` + +#### Response + +```curl +HTTP/1.1 200 OK +``` diff --git a/docs/api/index.html b/docs/api/index.html new file mode 100644 index 00000000..5076a8f8 --- /dev/null +++ b/docs/api/index.html @@ -0,0 +1,470 @@ + + + + + Access app API + + + + + + + + +

Access app API

Download OpenAPI specification:Download

Access app API endpoints

+

user-role-controller

updateUserRoleExpirationDate

Authorizations:
openId
Request Body schema: application/json
required
userRoleId
required
integer <int64>
endDate
string <date-time>

Responses

Request samples

Content type
application/json
{
  • "userRoleId": 0,
  • "endDate": "2019-08-24T14:15:22Z"
}

Response samples

Content type
application/json
{
  • "property1": 0,
  • "property2": 0
}

byRole

Authorizations:
openId
path Parameters
roleId
required
integer <int64>

Responses

Response samples

Content type
application/json
[
  • {
    }
]

deleteUserRole

Authorizations:
openId
path Parameters
id
required
integer <int64>

Responses

role-controller

rolesByApplication

Authorizations:
openId

Responses

Response samples

Content type
application/json
[
  • {
    }
]

updateRole

Authorizations:
openId
Request Body schema: application/json
required
id
integer <int64>
name
required
string
shortName
required
string
description
string
landingPage
string
defaultExpiryDays
integer <int32>
manageId
string
enforceEmailEquality
boolean
eduIDOnly
boolean
manageType
string
Enum: "SAML20_SP" "OIDC10_RP" "SAML20_IDP" "PROVISIONING"
blockExpiryDate
boolean
overrideSettingsAllowed
boolean
userRoleCount
integer <int64>
object (Auditable)
object

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "shortName": "string",
  • "description": "string",
  • "landingPage": "string",
  • "defaultExpiryDays": 0,
  • "manageId": "string",
  • "enforceEmailEquality": true,
  • "eduIDOnly": true,
  • "manageType": "SAML20_SP",
  • "blockExpiryDate": true,
  • "overrideSettingsAllowed": true,
  • "userRoleCount": 0,
  • "auditable": {
    },
  • "application": {
    }
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "shortName": "string",
  • "description": "string",
  • "landingPage": "string",
  • "defaultExpiryDays": 0,
  • "manageId": "string",
  • "enforceEmailEquality": true,
  • "eduIDOnly": true,
  • "manageType": "SAML20_SP",
  • "blockExpiryDate": true,
  • "overrideSettingsAllowed": true,
  • "userRoleCount": 0,
  • "auditable": {
    },
  • "application": {
    }
}

newRole

Authorizations:
openId
Request Body schema: application/json
required
id
integer <int64>
name
required
string
shortName
required
string
description
string
landingPage
string
defaultExpiryDays
integer <int32>
manageId
string
enforceEmailEquality
boolean
eduIDOnly
boolean
manageType
string
Enum: "SAML20_SP" "OIDC10_RP" "SAML20_IDP" "PROVISIONING"
blockExpiryDate
boolean
overrideSettingsAllowed
boolean
userRoleCount
integer <int64>
object (Auditable)
object

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "shortName": "string",
  • "description": "string",
  • "landingPage": "string",
  • "defaultExpiryDays": 0,
  • "manageId": "string",
  • "enforceEmailEquality": true,
  • "eduIDOnly": true,
  • "manageType": "SAML20_SP",
  • "blockExpiryDate": true,
  • "overrideSettingsAllowed": true,
  • "userRoleCount": 0,
  • "auditable": {
    },
  • "application": {
    }
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "shortName": "string",
  • "description": "string",
  • "landingPage": "string",
  • "defaultExpiryDays": 0,
  • "manageId": "string",
  • "enforceEmailEquality": true,
  • "eduIDOnly": true,
  • "manageType": "SAML20_SP",
  • "blockExpiryDate": true,
  • "overrideSettingsAllowed": true,
  • "userRoleCount": 0,
  • "auditable": {
    },
  • "application": {
    }
}

shortNameExists

Authorizations:
openId
Request Body schema: application/json
required
shortName
string
manageId
string
id
integer <int64>

Responses

Request samples

Content type
application/json
{
  • "shortName": "string",
  • "manageId": "string",
  • "id": 0
}

Response samples

Content type
application/json
{
  • "property1": true,
  • "property2": true
}

role

Authorizations:
openId
path Parameters
id
required
integer <int64>
query Parameters
required
object (User)

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "shortName": "string",
  • "description": "string",
  • "landingPage": "string",
  • "defaultExpiryDays": 0,
  • "manageId": "string",
  • "enforceEmailEquality": true,
  • "eduIDOnly": true,
  • "manageType": "SAML20_SP",
  • "blockExpiryDate": true,
  • "overrideSettingsAllowed": true,
  • "userRoleCount": 0,
  • "auditable": {
    },
  • "application": {
    }
}

deleteRole

Authorizations:
openId
path Parameters
id
required
integer <int64>

Responses

search_1

Authorizations:
openId
query Parameters
query
required
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]

invitation-controller

resendInvitation

Authorizations:
openId
path Parameters
id
required
integer <int64>

Responses

Response samples

Content type
application/json
{
  • "property1": 0,
  • "property2": 0
}

deleteInvitation

Authorizations:
openId
path Parameters
id
required
integer <int64>

Responses

accept

Authorizations:
openId
Request Body schema: application/json
required
hash
string
invitationId
integer <int64>

Responses

Request samples

Content type
application/json
{
  • "hash": "string",
  • "invitationId": 0
}

Response samples

Content type
application/json
{
  • "property1": 0,
  • "property2": 0
}

newInvitation

Authorizations:
openId
Request Body schema: application/json
required
intendedAuthority
required
string
Enum: "SUPER_USER" "INSTITUTION_ADMIN" "MANAGER" "INVITER" "GUEST"
message
string
enforceEmailEquality
boolean
eduIDOnly
boolean
invites
required
Array of strings
roleIdentifiers
Array of integers <int64> [ items <int64 > ]
roleExpiryDate
string <date-time>
expiryDate
required
string <date-time>

Responses

Request samples

Content type
application/json
{
  • "intendedAuthority": "SUPER_USER",
  • "message": "string",
  • "enforceEmailEquality": true,
  • "eduIDOnly": true,
  • "invites": [
    ],
  • "roleIdentifiers": [
    ],
  • "roleExpiryDate": "2019-08-24T14:15:22Z",
  • "expiryDate": "2019-08-24T14:15:22Z"
}

Response samples

Content type
application/json
{
  • "property1": 0,
  • "property2": 0
}

byRole_1

Authorizations:
openId
path Parameters
roleId
required
integer <int64>

Responses

Response samples

Content type
application/json
[
  • {
    }
]

getInvitation

Authorizations:
openId
query Parameters
hash
required
string

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "intendedAuthority": "SUPER_USER",
  • "status": "OPEN",
  • "email": "string",
  • "message": "string",
  • "enforceEmailEquality": true,
  • "eduIDOnly": true,
  • "createdAt": "2019-08-24T14:15:22Z",
  • "expiryDate": "2019-08-24T14:15:22Z",
  • "roleExpiryDate": "2019-08-24T14:15:22Z",
  • "roles": [
    ],
  • "emailEqualityConflict": true,
  • "inviter": {
    }
}

all

Authorizations:
openId

Responses

Response samples

Content type
application/json
[
  • {
    }
]

validation-controller

validate

Authorizations:
openId
Request Body schema: application/json
required
type
required
string
value
required
string

Responses

Request samples

Content type
application/json
{
  • "type": "string",
  • "value": "string"
}

Response samples

Content type
application/json
{
  • "property1": true,
  • "property2": true
}

user-controller

error

Authorizations:
openId
Request Body schema: application/json
required
property name*
additional property
object

Responses

Request samples

Content type
application/json
{
  • "property1": { },
  • "property2": { }
}

Response samples

Content type
application/json
{
  • "property1": 0,
  • "property2": 0
}

switchApp

Authorizations:
openId
query Parameters
app
required
string

Responses

Response samples

Content type
application/json
{
  • "contentType": "string"
}

search

Authorizations:
openId
query Parameters
query
required
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]

details

Authorizations:
openId
path Parameters
id
required
integer <int64>

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "sub": "string",
  • "superUser": true,
  • "eduPersonPrincipalName": "string",
  • "givenName": "string",
  • "familyName": "string",
  • "name": "string",
  • "schacHomeOrganization": "string",
  • "organizationGUID": "string",
  • "institutionAdmin": true,
  • "email": "string",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "lastActivity": "2019-08-24T14:15:22Z",
  • "userRoles": [
    ],
  • "applications": [
    ],
  • "institution": {
    }
}

me

Authorizations:
openId

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "sub": "string",
  • "superUser": true,
  • "eduPersonPrincipalName": "string",
  • "givenName": "string",
  • "familyName": "string",
  • "name": "string",
  • "schacHomeOrganization": "string",
  • "organizationGUID": "string",
  • "institutionAdmin": true,
  • "email": "string",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "lastActivity": "2019-08-24T14:15:22Z",
  • "userRoles": [
    ],
  • "applications": [
    ],
  • "institution": {
    }
}

logout

Authorizations:
openId

Responses

Response samples

Content type
application/json
{
  • "property1": 0,
  • "property2": 0
}

login

Authorizations:
openId
query Parameters
app
string
Default: "client"

Responses

Response samples

Content type
application/json
{
  • "contentType": "string"
}

config

Authorizations:
openId
query Parameters
required
object (User)

Responses

Response samples

Content type
application/json
{
  • "clientUrl": "string",
  • "welcomeUrl": "string",
  • "serverUrl": "string",
  • "serverWelcomeUrl": "string",
  • "eduidEntityId": "string",
  • "roleSearchRequired": true,
  • "pastDateAllowed": true,
  • "groupUrnPrefix": "string",
  • "authenticated": true,
  • "name": "string",
  • "missingAttributes": [
    ]
}

voot-controller

getGroupMemberships

Authorizations:
voot
path Parameters
unspecified_id
required
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]

system-controller

cron

Authorizations:
openId

Responses

Response samples

Content type
application/json
{
  • "property1": [
    ],
  • "property2": [
    ]
}

manage-controller

provisioning

Authorizations:
openId
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]

providers

Authorizations:
openId

Responses

Response samples

Content type
application/json
[
  • {
    }
]

providerById

Authorizations:
openId
path Parameters
type
required
string
Enum: "SAML20_SP" "OIDC10_RP" "SAML20_IDP" "PROVISIONING"
id
required
string

Responses

Response samples

Content type
application/json
{
  • "property1": { },
  • "property2": { }
}

applications

Authorizations:
openId

Responses

Response samples

Content type
application/json
{
  • "property1": [
    ],
  • "property2": [
    ]
}

user-life-cycle-controller

preview

Authorizations:
lifeCycle
path Parameters
userId
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string",
  • "name": "string",
  • "data": [
    ]
}

deprovision

Authorizations:
lifeCycle
path Parameters
userId
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string",
  • "name": "string",
  • "data": [
    ]
}

dryRun

Authorizations:
lifeCycle
path Parameters
userId
required
string

Responses

Response samples

Content type
application/json
{
  • "status": "string",
  • "name": "string",
  • "data": [
    ]
}

attribute-aggregator-controller

getGroupMemberships_1

Authorizations:
attributeAggregation
path Parameters
unspecified_id
required
string
query Parameters
SPentityID
required
string

Responses

Response samples

Content type
application/json
[
  • {
    }
]
+ + + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..56aa2a9a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,21 @@ +# Openconext-Invite + +## Intro + +This application provides an application to send email invites to users. +Once the invite is accepted, the new user roles can be sent to external systems +by [SCIM](https://datatracker.ietf.org/doc/html/rfc7643#section-4.1), graph API +or email (ticketing system). Also a +[VOOT](https://wiki.geant.org/display/gn3pjra3/VOOT+specifications) provider is +available for publishing the users' memberships. + +To test and demonstrate the application, an example client application is available. + +## Documentation + +- [API documentation](./api/) +- [SCIM description](./SCIM/) +- [Architecture design](./archi/?view=00685569-37a2-4756-b7b7-6f79dc6bdbd1) +- [Roles & permissions](./rights.md) +- [Code](https://github.com/OpenConext/OpenConext-Invite/) +- [Backlog](https://www.pivotaltracker.com/n/projects/2641889) diff --git a/provisioning-mock/src/main/resources/application.yml b/provisioning-mock/src/main/resources/application.yml index 61dd2cf0..8ff868d2 100644 --- a/provisioning-mock/src/main/resources/application.yml +++ b/provisioning-mock/src/main/resources/application.yml @@ -1,3 +1,5 @@ +--- + logging: level: org.springframework: INFO @@ -36,5 +38,3 @@ management: info: git: mode: full - - diff --git a/server/pom.xml b/server/pom.xml index 3fc610db..d4e2d76f 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -176,6 +176,34 @@ org.springframework.boot spring-boot-maven-plugin + + -Dspring.application.admin.enabled=true + + + + + start + stop + + + + + + org.springdoc + springdoc-openapi-maven-plugin + 1.4 + + http://localhost:8080/ui/api-docs + openapi.json + + + + integration-test + + generate + + + io.github.git-commit-id diff --git a/template.hbs b/template.hbs new file mode 100644 index 00000000..cef8b72a --- /dev/null +++ b/template.hbs @@ -0,0 +1,21 @@ + + + + + {{title}} + + + + {{{redocHead}}} + {{#unless disableGoogleFont}}{{/unless}} + + + {{{redocHTML}}} + +