From 8e2fa79f7f8414c6add69958d10a58e8854ad5d2 Mon Sep 17 00:00:00 2001 From: Lydia Date: Fri, 29 Apr 2022 13:55:48 -0700 Subject: [PATCH 1/9] ENDOC-495 --- .../create/ms/add-access-controls.md | 163 ++++++++++++------ 1 file changed, 107 insertions(+), 56 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index 08f146851f..6cefcd1f0c 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -1,77 +1,112 @@ ---- -sidebarDepth: 2 ---- # Role Based Access Controls ## Overview -This tutorial guides you through adding access controls to your existing Entando project. Security experts recommend following a practice known as `Defense in Depth` where security controls are placed in each layer of an architecture. This tutorial will help you setup such controls in both the frontend and backend of your Entando application. +Experts recommend following a practice known as Defense in Depth, where security controls are placed in each layer of an architecture. This tutorial guides you through adding access controls to your existing Entando project, in both the frontend and backend of your Entando Application. -For the purpose of this tutorial we'll use the simple Conference application from [this tutorial](./generate-microservices-and-micro-frontends.md) as a starting point. Please work through that tutorial if you have not already. +The simple Conference application found in the [Generate Microservices and Micro Frontends tutorial](./generate-microservices-and-micro-frontends.md) is used as a starting point. We recommend working through that tutorial for background and context. -The basic security setup for a blueprint-generated application allows any authenticated user to access the functionality contained in the MFEs and/or microservices. Our business requirement for this tutorial is to define two kinds of users in our application - `Conference Users` who can view the Conferences in the tableWidget, and `Conference Admins` who can view and also delete Conferences from the tableWidget. +The basic security setup for a blueprint-generated application allows any authenticated user to access the functionality contained in the MFEs and/or microservices. This tutorial defines two user roles for our application: -## Tutorial -Let's start by securing the list of Conferences so only our two user roles can view the list. +- `conference-user`: Permitted to view the Conferences in the tableWidget +- `conference-admin`: Permitted to view Conferences in the tableWidget, and also to delete Conferences from the tableWidget -1. Edit `ConferenceResource.java` located in the `src/main/java/com//.web.rest` directory. Modify the REST API `Conference:getAllConferences` method by adding the following annotation. + +## Apply and Verify Access Controls + +### Step 1: Secure the Conference list + +The list of Conferences must be visible to only the `conference-user` and `conference-admin` user roles. + +1. Go to the `src/main/java/com//.web.rest` directory +2. Open `ConferenceResource.java` +3. Modify the REST API `Conference:getAllConferences` method by adding the following annotation ``` @PreAuthorize("hasAnyAuthority('conference-user','conference-admin')") public List getAllConferences() { ``` -See the [Spring Security documentation](https://spring.io/projects/spring-security) for more details but this restricts use of the `getConference` method to users who have been assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. In local testing this defaults to the `internal` client but see notes below on how that works in production. +This confines use of the `getConference` method to users who are assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. -Now we should verify this security check is working. +> Note: In local testing, the default client is `internal`. Refer to the [Spring Security documentation](https://spring.io/projects/spring-security) for more information. -2. Start up your Keycloak, tableWidget MFE, and microservice. See [these instructions](./run-local.md) if you need a refresher but these are the basic commands using the ent CLI and Docker for keycloak. -``` +### Step 2: Run your project in a local developer environment +The following commands leverage the [ent CLI](../../../docs/reference/entando-cli.md). + +1. Start up your Keycloak instance +``` sh ent prj ext-keycloak start -ent prj be-test-run ``` -Using a separate cmdline: +2. Initialize the tableWidget MFE +``` sh +ent prj be-test-run ``` +3. Use a new cmd line to start the microservice +``` sh ent prj fe-test-run ``` +> Note: Refer to the [Run Blueprint-generated Microservices and Micro Frontends in Dev Mode tutorial](./run-local.md) for details. + +### Step 3: Access the tableWidget MFE + +1. In your browser, go to . This is typically the location of the tableWidget MFE. +2. Access the tableWidget MFE with the default credentials of `username: admin`, `password: admin` -3. Access the tableWidget MFE, typically on , using the default admin/admin account. +> Note: Once authenticated, the message "No conferences are available" is generated. If you check your browser +console, you should see a `403 (Forbidden)` error for the request made to `localhost:8080/services/conference/api/conferences`. This is expected because the admin user has not yet been granted the new role. -Once authenticated, you'll get the message "No conferences are available" and, if you check your browser console, you should see a `403 (Forbidden)` error for the request made to `localhost:8080/services/conference/api/conferences`. This is expected because we have not yet granted the new role to the admin user. +### Step 4: Login to Keycloak -Now let's give the admin user the correct role. +1. Go to +2. Login using the the default credentials of `username: admin`, `password: admin` -4. Login to keycloak on using the `admin/admin` credentials. +### Step 5: Create the `conference-user` and `conference-admin` roles -First we need to create the two roles per our requirements. We're going to add the roles to the `internal` client because it's the one configured by default in the Spring Boot application.yml. +Add the `conference-user` and `conference-admin` roles to the `internal` client. -5. Go to `Clients → internal → Roles` and click `Add Role` -6. Fill in the `Role Name` with `conference-admin` and click `Save` -7. Repeat steps 5-6 to create the `conference-user` role. +1. Go to `Clients` → `internal` → `Roles` +2. Click `Add Role` +3. Fill in the `Role Name` with `conference-admin` +4. Click `Save` +5. Repeat these steps to create the `conference-user` role -Now we need to map this role to our user. +> Note: The `internal` client is configured by default in the Spring Boot `application.yml`. -8. Go to `Users → View all users → admin → Role Mappings` -9. Select `internal` for the `Client Roles` and then move `conference-user` from `Available Roles` to `Assigned Roles` -10. Go back to the MFE and you should now see the full list of Conferences. +### Step 6: Map the `conference-user` role to the admin user -We've now successfully secured the `getAllConferences` API but we have more to do. The admin user was granted just the `conference-user` role but still has access to delete Conferences. We need to lock that down. +To secure the `getAllConferences` API: -11. Go back into the `ConferenceResource.java` file and add this annotation to the `deleteConference` method: +1. Go to `Users` → `View all users` → `admin` → `Role Mappings` +2. Select `internal` for the `Client Roles` +3. Move `conference-user` from `Available Roles` to `Assigned Roles` +4. Return to the MFE to confirm you see the full list of Conferences +### Step 7: Restrict the ability to delete Conferences + +The `conference-user` role grants the admin user permission to delete Conferences. To restrict the delete method to the `conference-admin` role: + +1. Go to the `src/main/java/com//.web.rest` directory +2. Open `ConferenceResource.java` +3. Modify the `deleteConference` method by adding the following annotation ``` @PreAuthorize("hasAuthority('conference-admin')") public ResponseEntity deleteConference(@PathVariable Long id) { ``` -Here we're restricting the delete method to only the `conference-admin` role. -12. Restart the microservice. By default this will include rebuilding any changed source files. -13. Once the microservice is available, go back to the MFE and try deleting one of the Conferences in the list. You should be able to attempt the delete in the UI but you'll get a 403 error in the browser console and an error like this in the service logs: +To verify that a user without the `conference-admin` role is unable to call the delete API: + +1. Restart the microservice. By default this includes rebuilding any changed source files. +2. Once the microservice is available, return to the MFE and try deleting one of the Conferences in the list +3. Verify that attempting to delete via the UI generates a `403 error` in the browser console and an error in the service logs similiar to the following: ``` 2021-03-22 15:56:16.205 WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied ``` -That's exactly what we wanted! This demonstrates that a user without `conference-admin` is unable to call the delete API. -Next, let's update the MFE so a user without the `conference-admin` authority cannot even see the delete button in the UI. +### Step 8: Hide the delete button -14. Edit the `ConferenceTableContainer.js` under `ui/widgets/conference/tableWidget/src/components`. Replace the onDelete logic with an additional check on the user's authorities. +The MFE UI can be updated to hide the delete button from a user without the `conference-admin` authority. The key logic checks whether the `internal` client role `conference-admin` is mapped to the current user via the hasResourceRole call. + +1. Go to the `ui/widgets/conference/tableWidget/src/components` directory +2. Open `ConferenceTableContainer.js` +3. Replace the `onDelete` logic with an additional user permissions ``` const isAdmin = (keycloak && keycloak.authenticate) ? keycloak.hasResourceRole("conference-admin", "internal"): false; const showDelete = onDelete && isAdmin; @@ -79,31 +114,48 @@ Next, let's update the MFE so a user without the `conference-admin` authority ca const Actions = ({ item }) => showDelete ? ( ``` +4. Confirm that the delete icon is no longer visible in the MFE, which should have automatically reloaded -The key logic there is the hasResourceRole call which checks whether the `internal` client role `conference-admin` was mapped to the current user. - -15. View the MFE (whch should have automatically reloaded) and you should see that the delete icon is no longer visible, matching the admin's current permissions. We've now verified that a user with just `conference-user` can neither see the delete action in the UI nor call its corresponding API. +### Step 9: Grant and verify delete permissions -Next, let's promote the admin user to a full `conference-admin` so they can delete Conferences. +Promote the admin user to a full `conference-admin` to reinstate the ability to delete Conferences. -16. Go back into Keycloak at , then go to `Users → View all users → admin → Role Mappings`, and also give the user the `conference-admin` role. - -17. Reload the MFE. The delete icons should now be visible and you should be able to successfully delete a Conference from the list. This satisfies our original business requirement. +1. Return to Keycloak at +2. Go to `Users` → `View all users` → `admin` → `Role Mappings` +3. Give the user the `conference-admin` role +4. Reload the MFE +5. Confirm the delete icon is visible and you should be able to +6. Confirm a Conference can be successfully deleted from the list ## Notes ### Realm Roles versus Client Authorities -This tutorial made use of authorities which in Keycloak are Roles mapped to a User for a specific Client. You could also make use of higher-level Realm Roles assigned directly to users, e.g. `ROLE_ADMIN`. That will work but can result in collisions between applications if they happen to use the same roles. +This tutorial utilizes authorities, which (in Keycloak) are roles mapped to a user for a specific client. It is possible to assign higher-level Realm Roles directly to users, e.g. `ROLE_ADMIN`, but this can result in collisions between applications using the same roles. + +To implement Realm-assigned roles, the code above must be modified: +- In the backend, use the annotation `@Secured('ROLE_ADMIN)` or `@PreAuthorize(hasRole('ROLE_ADMIN'))` +- In the frontend, use `keycloak.hasRealmRole` instead of `keycloak.hasResourceRole` + +See the [Spring Security page](https://www.baeldung.com/spring-security-check-user-role) for more examples. + +### Local vs. Kubernetes Testing +This tutorial leverages the `internal` client, which is configured in the microservice via the `application.yml`. Client roles are manually created and assigned in Keycloak. + +In Kubernetes, Entando will automatically create client roles per the bundle plugin definition (see the [plugin definition](../../../docs/curate/ecr-bundle-details.md) for more information). These roles are created for the client specific to the microservice, e.g. `-conference-server`. The client name is injected as an environment variable into the plugin container, so the annotations noted above will work in both local and Kubernetes environments. -If you choose to use Realm-assigned roles then the code above would need to change. In the backend, use the following annotations: `@Secured('ROLE_ADMIN)` or `@PreAuthorize(hasRole('ROLE_ADMIN'))`. In the frontend, use `keycloak.hasRealmRole` instead of `keycloak.hasResourceRole`. See the [Spring Security page](https://www.baeldung.com/spring-security-check-user-role) for more examples. +#### Modify Security Checks for Kubernetes -### Local versus Kubernetes Testing -This tutorial also makes use of the `internal` client configured in the microservice via the application.yml with roles manually created and assigned in Keycloak. In Kubernetes, Entando will automatically create client roles per the bundle plugin definition (see the plugin definition [here](../../../docs/curate/ecr-bundle-details.md) for more information). Those roles will be created for the client specific to the microservice itself, e.g. `-conference-server`. This client name will be injected as an environment variable into the plugin container itself so the annotations noted above will work both in local and Kubernetes environments. +The MFE authorization checks in this tutorial explicitly note the client ID, e.g. `internal`. The following options modify the checks to work in Kubernetes: -The MFE authorization checks in the tutorial explicitly note the client id, e.g. `internal`, which won't work in Kubernetes. There are a couple options here: -1) Change the application.yml clientId under `security.oauth2.client.registration.oidc` to match the Kubernetes clientId. That's the most secure and allows the MFE checks to work the same in both local and Kubernetes environments. However, you not be be able to use the same clientId depending on how the microservice is deployed -2) An alternative is to broaden the MFE authorization check to look for a named role on any client. This could result in overlap with other clients but with appropriately named roles (e.g. prefixed by feature, e.g. `conference-admin`) this could be the most flexible option. This can be provided via a helper function, e.g. in `api/helpers.js`: +1) Change the `application.yml` clientId under `security.oauth2.client.registration.oidc` to match the Kubernetes clientId. + + This is the most secure option and allows the MFE checks to work identically in both local and Kubernetes environments. However, you may not be be able to use the same clientId, depending on how the microservice is deployed. + +2) Broaden the MFE authorization check to look for a named role on any client. + + This could result in overlap with other clients, but with appropriately named roles (e.g. prefixed by feature, e.g. `conference-admin`) this is the most flexible option. It can be achieved via a helper function, e.g. in `api/helpers.js`, and results in a simpler role check: ``` -//Check if the authenticated user has the clientRole for any keycloak clients +// Add helper function +// Check if the authenticated user has the clientRole for any Keycloak clients export const hasKeycloakClientRole = clientRole => { if (getKeycloakToken()) { const { resourceAccess } = window.entando.keycloak; @@ -119,11 +171,10 @@ export const hasKeycloakClientRole = clientRole => { } return false; }; -``` -This would result in a simpler role check: -``` - const isAdmin = hasKeycloakClientRole('conference-admin'); + +// Perform role check +const isAdmin = hasKeycloakClientRole('conference-admin'); ``` -### Debugging -In both local and Kubernetes environments, the default blueprint javascript will make a global variable available in the browser, e.g. `window.entando.keycloak`. Examining this variable can help diagnose issues with assigned roles and authorities. In some cases you may need to logout of Entando and re-authenticate in order to get the latest role assignments. \ No newline at end of file +### Troubleshooting +In both local and Kubernetes environments, the default Blueprint Javascript provides a global variable in the browser, e.g. `window.entando.keycloak`. Examining this variable can help diagnose issues with assigned roles and authorities. In some cases, you may need to logout of Entando and reauthenticate for the latest role assignments to be applied. \ No newline at end of file From b1e4b942a63a00ef74ab814f47b9854c31ffb0c2 Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Mon, 2 May 2022 12:10:51 -0700 Subject: [PATCH 2/9] ENDOC-495 PR feedback --- .../tutorials/create/ms/add-access-controls.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index 6cefcd1f0c..f8e35be4ce 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -1,7 +1,7 @@ # Role Based Access Controls ## Overview -Experts recommend following a practice known as Defense in Depth, where security controls are placed in each layer of an architecture. This tutorial guides you through adding access controls to your existing Entando project, in both the frontend and backend of your Entando Application. +Experts recommend following a practice known as Defense in Depth where security controls are placed in each layer of an architecture. This tutorial guides you through adding access controls to your existing Entando project, in both the frontend and backend of your Entando Application. The simple Conference application found in the [Generate Microservices and Micro Frontends tutorial](./generate-microservices-and-micro-frontends.md) is used as a starting point. We recommend working through that tutorial for background and context. @@ -19,7 +19,7 @@ The list of Conferences must be visible to only the `conference-user` and `confe 1. Go to the `src/main/java/com//.web.rest` directory 2. Open `ConferenceResource.java` -3. Modify the REST API `Conference:getAllConferences` method by adding the following annotation +3. Modify the REST API `Conference:getAllConferences` method by adding the following annotation: ``` @PreAuthorize("hasAnyAuthority('conference-user','conference-admin')") public List getAllConferences() { @@ -85,10 +85,10 @@ The `conference-user` role grants the admin user permission to delete Conference 1. Go to the `src/main/java/com//.web.rest` directory 2. Open `ConferenceResource.java` -3. Modify the `deleteConference` method by adding the following annotation +3. Modify the `deleteConference` method by adding the following annotation: ``` @PreAuthorize("hasAuthority('conference-admin')") - public ResponseEntity deleteConference(@PathVariable Long id) { + public ResponseEntity deleteConference(@PathVariable Long ID) { ``` To verify that a user without the `conference-admin` role is unable to call the delete API: @@ -106,7 +106,7 @@ The MFE UI can be updated to hide the delete button from a user without the `con 1. Go to the `ui/widgets/conference/tableWidget/src/components` directory 2. Open `ConferenceTableContainer.js` -3. Replace the `onDelete` logic with an additional user permissions +3. Replace the `onDelete` logic with an additional user permission: ``` const isAdmin = (keycloak && keycloak.authenticate) ? keycloak.hasResourceRole("conference-admin", "internal"): false; const showDelete = onDelete && isAdmin; @@ -129,7 +129,7 @@ Promote the admin user to a full `conference-admin` to reinstate the ability to ## Notes ### Realm Roles versus Client Authorities -This tutorial utilizes authorities, which (in Keycloak) are roles mapped to a user for a specific client. It is possible to assign higher-level Realm Roles directly to users, e.g. `ROLE_ADMIN`, but this can result in collisions between applications using the same roles. +This tutorial utilizes authorities. In Keycloak, authorities are roles mapped to a user for a specific client. It is possible to assign higher-level Realm Roles directly to users, e.g. `ROLE_ADMIN`, but this can result in collisions between applications using the same roles. To implement Realm-assigned roles, the code above must be modified: - In the backend, use the annotation `@Secured('ROLE_ADMIN)` or `@PreAuthorize(hasRole('ROLE_ADMIN'))` @@ -144,15 +144,15 @@ In Kubernetes, Entando will automatically create client roles per the bundle plu #### Modify Security Checks for Kubernetes -The MFE authorization checks in this tutorial explicitly note the client ID, e.g. `internal`. The following options modify the checks to work in Kubernetes: +In this tutorial, the MFE authorization checks explicitly note the client ID, e.g. `internal`. The following options modify the checks to work in Kubernetes: -1) Change the `application.yml` clientId under `security.oauth2.client.registration.oidc` to match the Kubernetes clientId. +1) Change the `application.yml` client ID under `security.oauth2.client.registration.oidc` to match the Kubernetes client ID. This is the most secure option and allows the MFE checks to work identically in both local and Kubernetes environments. However, you may not be be able to use the same clientId, depending on how the microservice is deployed. 2) Broaden the MFE authorization check to look for a named role on any client. - This could result in overlap with other clients, but with appropriately named roles (e.g. prefixed by feature, e.g. `conference-admin`) this is the most flexible option. It can be achieved via a helper function, e.g. in `api/helpers.js`, and results in a simpler role check: + This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. prefixed by feature, e.g. `conference-admin`). It can be achieved via a helper function, e.g. in `api/helpers.js`, and results in a simpler role check: ``` // Add helper function // Check if the authenticated user has the clientRole for any Keycloak clients From 26c6d8c7d3360b39f32f63a85db06f563d8dd407 Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Mon, 2 May 2022 13:17:32 -0700 Subject: [PATCH 3/9] ENDOC-495 Format placeholders --- vuepress/docs/next/tutorials/create/ms/add-access-controls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index f8e35be4ce..5a1192d451 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -17,7 +17,7 @@ The basic security setup for a blueprint-generated application allows any authen The list of Conferences must be visible to only the `conference-user` and `conference-admin` user roles. -1. Go to the `src/main/java/com//.web.rest` directory +1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME.web.rest` directory 2. Open `ConferenceResource.java` 3. Modify the REST API `Conference:getAllConferences` method by adding the following annotation: ``` From b93fb661fb1c88ddd841e03dea28888a2d8b14dc Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Wed, 4 May 2022 08:49:57 -0700 Subject: [PATCH 4/9] ENDOC-495 PR feedback --- .../create/ms/add-access-controls.md | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index 5a1192d451..0123b0c37c 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -17,33 +17,38 @@ The basic security setup for a blueprint-generated application allows any authen The list of Conferences must be visible to only the `conference-user` and `conference-admin` user roles. -1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME.web.rest` directory +1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory 2. Open `ConferenceResource.java` -3. Modify the REST API `Conference:getAllConferences` method by adding the following annotation: +3. Add the following to the list of imports: +``` + import org.springframework.security.access.prepost.PreAuthorize; +``` +4. Modify the REST API `Conference:getAllConferences` method by preceding it with the annotation below: ``` @PreAuthorize("hasAnyAuthority('conference-user','conference-admin')") - public List getAllConferences() { ``` -This confines use of the `getConference` method to users who are assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. +This confines use of the `getAllConferences` method to users who are assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. > Note: In local testing, the default client is `internal`. Refer to the [Spring Security documentation](https://spring.io/projects/spring-security) for more information. ### Step 2: Run your project in a local developer environment -The following commands leverage the [ent CLI](../../../docs/reference/entando-cli.md). +The following commands must be run from your project directory. They leverage the [ent CLI](../../../docs/reference/entando-cli.md). + +> Note: Refer to the [Run Blueprint-generated Microservices and Micro Frontends in Dev Mode tutorial](./run-local.md) for details. 1. Start up your Keycloak instance ``` sh ent prj ext-keycloak start ``` -2. Initialize the tableWidget MFE +2. Start the microservice in new shell ``` sh ent prj be-test-run ``` -3. Use a new cmd line to start the microservice +3. Start the tableWidget MFE ``` sh ent prj fe-test-run ``` -> Note: Refer to the [Run Blueprint-generated Microservices and Micro Frontends in Dev Mode tutorial](./run-local.md) for details. +4. When prompted to select a widget to run, choose the option corresponding to the tableWidget, e.g. ui/widgets/conference/tableWidget ### Step 3: Access the tableWidget MFE @@ -83,23 +88,24 @@ To secure the `getAllConferences` API: The `conference-user` role grants the admin user permission to delete Conferences. To restrict the delete method to the `conference-admin` role: -1. Go to the `src/main/java/com//.web.rest` directory +1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory 2. Open `ConferenceResource.java` -3. Modify the `deleteConference` method by adding the following annotation: +3. Modify the `deleteConference` method by preceding it with the following annotation: ``` @PreAuthorize("hasAuthority('conference-admin')") - public ResponseEntity deleteConference(@PathVariable Long ID) { ``` To verify that a user without the `conference-admin` role is unable to call the delete API: 1. Restart the microservice. By default this includes rebuilding any changed source files. 2. Once the microservice is available, return to the MFE and try deleting one of the Conferences in the list -3. Verify that attempting to delete via the UI generates a `403 error` in the browser console and an error in the service logs similiar to the following: +3. Verify that attempting to delete via the UI generates a `403 error` in the browser console and an error in the service logs similar to the following: ``` 2021-03-22 15:56:16.205 WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied ``` +This demonstrates that a user without `conference-admin` is unable to call the delete API. + ### Step 8: Hide the delete button The MFE UI can be updated to hide the delete button from a user without the `conference-admin` authority. The key logic checks whether the `internal` client role `conference-admin` is mapped to the current user via the hasResourceRole call. From 0d200fa83fc2087a3ecd982fd2403ef1e781b674 Mon Sep 17 00:00:00 2001 From: Lydia Pedersen Date: Thu, 5 May 2022 16:30:35 -0700 Subject: [PATCH 5/9] ENDOC-495 Cleaned up --- .../docs/next/tutorials/create/ms/add-access-controls.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index 0123b0c37c..64bc1bc5b7 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -40,7 +40,7 @@ The following commands must be run from your project directory. They leverage th ``` sh ent prj ext-keycloak start ``` -2. Start the microservice in new shell +2. Start the microservice in a new shell ``` sh ent prj be-test-run ``` @@ -77,7 +77,7 @@ Add the `conference-user` and `conference-admin` roles to the `internal` client. ### Step 6: Map the `conference-user` role to the admin user -To secure the `getAllConferences` API: +To grant access to the `getAllConferences` API: 1. Go to `Users` → `View all users` → `admin` → `Role Mappings` 2. Select `internal` for the `Client Roles` @@ -104,8 +104,6 @@ To verify that a user without the `conference-admin` role is unable to call the 2021-03-22 15:56:16.205 WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied ``` -This demonstrates that a user without `conference-admin` is unable to call the delete API. - ### Step 8: Hide the delete button The MFE UI can be updated to hide the delete button from a user without the `conference-admin` authority. The key logic checks whether the `internal` client role `conference-admin` is mapped to the current user via the hasResourceRole call. @@ -130,7 +128,7 @@ Promote the admin user to a full `conference-admin` to reinstate the ability to 2. Go to `Users` → `View all users` → `admin` → `Role Mappings` 3. Give the user the `conference-admin` role 4. Reload the MFE -5. Confirm the delete icon is visible and you should be able to +5. Confirm the delete icon is visible 6. Confirm a Conference can be successfully deleted from the list ## Notes From b66486a3177a333758a773297444981f51441618 Mon Sep 17 00:00:00 2001 From: Nathan Shaw Date: Tue, 23 Aug 2022 16:23:32 -0400 Subject: [PATCH 6/9] ENDOC-495 Add workarounds to the RBAC tutorial --- .../create/ms/add-access-controls.md | 35 ++- .../create/ms/add-access-controls.md | 208 ++++++++++++------ 2 files changed, 178 insertions(+), 65 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index 64bc1bc5b7..dc2fe152fd 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -31,6 +31,35 @@ This confines use of the `getAllConferences` method to users who are assigned ei > Note: In local testing, the default client is `internal`. Refer to the [Spring Security documentation](https://spring.io/projects/spring-security) for more information. +We also need to modify the blueprint JWT handling to deal with a recent change to the Spring libraries. + +5. Edit `src/main/java/com/mycompany/myapp/config/SecurityConfiguration.java` and make two changes. + * Add this code after the other @Value fields +``` java +@Value("${spring.security.oauth2.client.registration.oidc.client-id}") +private String clientId; +``` + * Modify the following call in the `authenticationConverter` method to provide the clientId field +``` java +jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter(clientId)); + ``` +6. Now modify + `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter`.java` to accept the clientId. Three changes are required. + * Remove the @Component annotation on the class definition: + ```java + @Component + ``` + * Remove the @Value annotation on the clientId field +```java +@Value("${spring.security.oauth2.client.registration.oidc.client-id}") +``` + * Modify the constructor to accept the clientId +```java + public JwtGrantedAuthorityConverter(String clientId) { + this.clientId = clientId; + } +``` + ### Step 2: Run your project in a local developer environment The following commands must be run from your project directory. They leverage the [ent CLI](../../../docs/reference/entando-cli.md). @@ -48,7 +77,7 @@ ent prj be-test-run ``` sh ent prj fe-test-run ``` -4. When prompted to select a widget to run, choose the option corresponding to the tableWidget, e.g. ui/widgets/conference/tableWidget +4. When prompted to select a widget to run, choose the option corresponding to the tableWidget, e.g. `ui/widgets/conference/tableWidget` ### Step 3: Access the tableWidget MFE @@ -101,7 +130,7 @@ To verify that a user without the `conference-admin` role is unable to call the 2. Once the microservice is available, return to the MFE and try deleting one of the Conferences in the list 3. Verify that attempting to delete via the UI generates a `403 error` in the browser console and an error in the service logs similar to the following: ``` -2021-03-22 15:56:16.205 WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied +WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied ``` ### Step 8: Hide the delete button @@ -112,7 +141,7 @@ The MFE UI can be updated to hide the delete button from a user without the `con 2. Open `ConferenceTableContainer.js` 3. Replace the `onDelete` logic with an additional user permission: ``` - const isAdmin = (keycloak && keycloak.authenticate) ? keycloak.hasResourceRole("conference-admin", "internal"): false; + const isAdmin = (keycloak && keycloak.authenticated) ? keycloak.hasResourceRole("conference-admin", "internal"): false; const showDelete = onDelete && isAdmin; const Actions = ({ item }) => diff --git a/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md b/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md index 08f146851f..5f14947324 100644 --- a/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md @@ -1,109 +1,194 @@ ---- -sidebarDepth: 2 ---- # Role Based Access Controls ## Overview -This tutorial guides you through adding access controls to your existing Entando project. Security experts recommend following a practice known as `Defense in Depth` where security controls are placed in each layer of an architecture. This tutorial will help you setup such controls in both the frontend and backend of your Entando application. +Experts recommend following a practice known as Defense in Depth where security controls are placed in each layer of an architecture. This tutorial guides you through adding access controls to your existing Entando project, in both the frontend and backend of your Entando Application. -For the purpose of this tutorial we'll use the simple Conference application from [this tutorial](./generate-microservices-and-micro-frontends.md) as a starting point. Please work through that tutorial if you have not already. +The simple Conference application found in the [Generate Microservices and Micro Frontends tutorial](./generate-microservices-and-micro-frontends.md) is used as a starting point. We recommend working through that tutorial for background and context. -The basic security setup for a blueprint-generated application allows any authenticated user to access the functionality contained in the MFEs and/or microservices. Our business requirement for this tutorial is to define two kinds of users in our application - `Conference Users` who can view the Conferences in the tableWidget, and `Conference Admins` who can view and also delete Conferences from the tableWidget. +The basic security setup for a blueprint-generated application allows any authenticated user to access the functionality contained in the MFEs and/or microservices. This tutorial defines two user roles for our application: -## Tutorial -Let's start by securing the list of Conferences so only our two user roles can view the list. +- `conference-user`: Permitted to view the Conferences in the tableWidget +- `conference-admin`: Permitted to view Conferences in the tableWidget, and also to delete Conferences from the tableWidget -1. Edit `ConferenceResource.java` located in the `src/main/java/com//.web.rest` directory. Modify the REST API `Conference:getAllConferences` method by adding the following annotation. + +## Apply and Verify Access Controls + +### Step 1: Secure the Conference list + +The list of Conferences must be visible to only the `conference-user` and `conference-admin` user roles. + +1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory +2. Open `ConferenceResource.java` +3. Add the following to the list of imports: +``` + import org.springframework.security.access.prepost.PreAuthorize; +``` +4. Modify the REST API `Conference:getAllConferences` method by preceding it with the annotation below: ``` @PreAuthorize("hasAnyAuthority('conference-user','conference-admin')") - public List getAllConferences() { ``` -See the [Spring Security documentation](https://spring.io/projects/spring-security) for more details but this restricts use of the `getConference` method to users who have been assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. In local testing this defaults to the `internal` client but see notes below on how that works in production. +This confines use of the `getAllConferences` method to users who are assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. + +> Note: In local testing, the default client is `internal`. Refer to the [Spring Security documentation](https://spring.io/projects/spring-security) for more information. + +We also need to modify the blueprint JWT handling to deal with a recent change to the Spring libraries. + +5. Edit `src/main/java/com/mycompany/myapp/config/SecurityConfiguration.java` and make two changes. +* Add this code after the other @Value fields +``` java +@Value("${spring.security.oauth2.client.registration.oidc.client-id}") +private String clientId; +``` +* Modify the following call in the `authenticationConverter` method to provide the clientId field +``` java +jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter(clientId)); + ``` +6. Now modify + `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter`.java` to accept the clientId. Three changes are required. +* Remove the @Component annotation on the class definition: + ```java + @Component + ``` +* Remove the @Value annotation on the clientId field +```java +@Value("${spring.security.oauth2.client.registration.oidc.client-id}") +``` +* Modify the constructor to accept the clientId +```java + public JwtGrantedAuthorityConverter(String clientId) { + this.clientId = clientId; + } +``` -Now we should verify this security check is working. +### Step 2: Run your project in a local developer environment +The following commands must be run from your project directory. They leverage the [ent CLI](../../../docs/reference/entando-cli.md). -2. Start up your Keycloak, tableWidget MFE, and microservice. See [these instructions](./run-local.md) if you need a refresher but these are the basic commands using the ent CLI and Docker for keycloak. -``` +> Note: Refer to the [Run Blueprint-generated Microservices and Micro Frontends in Dev Mode tutorial](./run-local.md) for details. + +1. Start up your Keycloak instance +``` sh ent prj ext-keycloak start -ent prj be-test-run ``` -Using a separate cmdline: +2. Start the microservice in a new shell +``` sh +ent prj be-test-run ``` +3. Start the tableWidget MFE +``` sh ent prj fe-test-run ``` +4. When prompted to select a widget to run, choose the option corresponding to the tableWidget, e.g. `ui/widgets/conference/tableWidget` + +### Step 3: Access the tableWidget MFE + +1. In your browser, go to . This is typically the location of the tableWidget MFE. +2. Access the tableWidget MFE with the default credentials of `username: admin`, `password: admin` + +> Note: Once authenticated, the message "No conferences are available" is generated. If you check your browser +console, you should see a `403 (Forbidden)` error for the request made to `localhost:8080/services/conference/api/conferences`. This is expected because the admin user has not yet been granted the new role. + +### Step 4: Login to Keycloak -3. Access the tableWidget MFE, typically on , using the default admin/admin account. +1. Go to +2. Login using the the default credentials of `username: admin`, `password: admin` -Once authenticated, you'll get the message "No conferences are available" and, if you check your browser console, you should see a `403 (Forbidden)` error for the request made to `localhost:8080/services/conference/api/conferences`. This is expected because we have not yet granted the new role to the admin user. +### Step 5: Create the `conference-user` and `conference-admin` roles -Now let's give the admin user the correct role. +Add the `conference-user` and `conference-admin` roles to the `internal` client. -4. Login to keycloak on using the `admin/admin` credentials. +1. Go to `Clients` → `internal` → `Roles` +2. Click `Add Role` +3. Fill in the `Role Name` with `conference-admin` +4. Click `Save` +5. Repeat these steps to create the `conference-user` role -First we need to create the two roles per our requirements. We're going to add the roles to the `internal` client because it's the one configured by default in the Spring Boot application.yml. - -5. Go to `Clients → internal → Roles` and click `Add Role` -6. Fill in the `Role Name` with `conference-admin` and click `Save` -7. Repeat steps 5-6 to create the `conference-user` role. +> Note: The `internal` client is configured by default in the Spring Boot `application.yml`. -Now we need to map this role to our user. +### Step 6: Map the `conference-user` role to the admin user -8. Go to `Users → View all users → admin → Role Mappings` -9. Select `internal` for the `Client Roles` and then move `conference-user` from `Available Roles` to `Assigned Roles` -10. Go back to the MFE and you should now see the full list of Conferences. +To grant access to the `getAllConferences` API: -We've now successfully secured the `getAllConferences` API but we have more to do. The admin user was granted just the `conference-user` role but still has access to delete Conferences. We need to lock that down. +1. Go to `Users` → `View all users` → `admin` → `Role Mappings` +2. Select `internal` for the `Client Roles` +3. Move `conference-user` from `Available Roles` to `Assigned Roles` +4. Return to the MFE to confirm you see the full list of Conferences -11. Go back into the `ConferenceResource.java` file and add this annotation to the `deleteConference` method: +### Step 7: Restrict the ability to delete Conferences +The `conference-user` role grants the admin user permission to delete Conferences. To restrict the delete method to the `conference-admin` role: + +1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory +2. Open `ConferenceResource.java` +3. Modify the `deleteConference` method by preceding it with the following annotation: ``` @PreAuthorize("hasAuthority('conference-admin')") - public ResponseEntity deleteConference(@PathVariable Long id) { ``` -Here we're restricting the delete method to only the `conference-admin` role. -12. Restart the microservice. By default this will include rebuilding any changed source files. -13. Once the microservice is available, go back to the MFE and try deleting one of the Conferences in the list. You should be able to attempt the delete in the UI but you'll get a 403 error in the browser console and an error like this in the service logs: +To verify that a user without the `conference-admin` role is unable to call the delete API: + +1. Restart the microservice. By default this includes rebuilding any changed source files. +2. Once the microservice is available, return to the MFE and try deleting one of the Conferences in the list +3. Verify that attempting to delete via the UI generates a `403 error` in the browser console and an error in the service logs similar to the following: ``` -2021-03-22 15:56:16.205 WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied +WARN 3208 --- [ XNIO-1 task-3] o.z.problem.spring.common.AdviceTraits : Forbidden: Access is denied ``` -That's exactly what we wanted! This demonstrates that a user without `conference-admin` is unable to call the delete API. -Next, let's update the MFE so a user without the `conference-admin` authority cannot even see the delete button in the UI. +### Step 8: Hide the delete button -14. Edit the `ConferenceTableContainer.js` under `ui/widgets/conference/tableWidget/src/components`. Replace the onDelete logic with an additional check on the user's authorities. +The MFE UI can be updated to hide the delete button from a user without the `conference-admin` authority. The key logic checks whether the `internal` client role `conference-admin` is mapped to the current user via the hasResourceRole call. + +1. Go to the `ui/widgets/conference/tableWidget/src/components` directory +2. Open `ConferenceTableContainer.js` +3. Replace the `onDelete` logic with an additional user permission: ``` - const isAdmin = (keycloak && keycloak.authenticate) ? keycloak.hasResourceRole("conference-admin", "internal"): false; + const isAdmin = (keycloak && keycloak.authenticated) ? keycloak.hasResourceRole("conference-admin", "internal"): false; const showDelete = onDelete && isAdmin; const Actions = ({ item }) => showDelete ? ( ``` +4. Confirm that the delete icon is no longer visible in the MFE, which should have automatically reloaded -The key logic there is the hasResourceRole call which checks whether the `internal` client role `conference-admin` was mapped to the current user. - -15. View the MFE (whch should have automatically reloaded) and you should see that the delete icon is no longer visible, matching the admin's current permissions. We've now verified that a user with just `conference-user` can neither see the delete action in the UI nor call its corresponding API. +### Step 9: Grant and verify delete permissions -Next, let's promote the admin user to a full `conference-admin` so they can delete Conferences. +Promote the admin user to a full `conference-admin` to reinstate the ability to delete Conferences. -16. Go back into Keycloak at , then go to `Users → View all users → admin → Role Mappings`, and also give the user the `conference-admin` role. - -17. Reload the MFE. The delete icons should now be visible and you should be able to successfully delete a Conference from the list. This satisfies our original business requirement. +1. Return to Keycloak at +2. Go to `Users` → `View all users` → `admin` → `Role Mappings` +3. Give the user the `conference-admin` role +4. Reload the MFE +5. Confirm the delete icon is visible +6. Confirm a Conference can be successfully deleted from the list ## Notes ### Realm Roles versus Client Authorities -This tutorial made use of authorities which in Keycloak are Roles mapped to a User for a specific Client. You could also make use of higher-level Realm Roles assigned directly to users, e.g. `ROLE_ADMIN`. That will work but can result in collisions between applications if they happen to use the same roles. +This tutorial utilizes authorities. In Keycloak, authorities are roles mapped to a user for a specific client. It is possible to assign higher-level Realm Roles directly to users, e.g. `ROLE_ADMIN`, but this can result in collisions between applications using the same roles. + +To implement Realm-assigned roles, the code above must be modified: +- In the backend, use the annotation `@Secured('ROLE_ADMIN)` or `@PreAuthorize(hasRole('ROLE_ADMIN'))` +- In the frontend, use `keycloak.hasRealmRole` instead of `keycloak.hasResourceRole` + +See the [Spring Security page](https://www.baeldung.com/spring-security-check-user-role) for more examples. + +### Local vs. Kubernetes Testing +This tutorial leverages the `internal` client, which is configured in the microservice via the `application.yml`. Client roles are manually created and assigned in Keycloak. -If you choose to use Realm-assigned roles then the code above would need to change. In the backend, use the following annotations: `@Secured('ROLE_ADMIN)` or `@PreAuthorize(hasRole('ROLE_ADMIN'))`. In the frontend, use `keycloak.hasRealmRole` instead of `keycloak.hasResourceRole`. See the [Spring Security page](https://www.baeldung.com/spring-security-check-user-role) for more examples. +In Kubernetes, Entando will automatically create client roles per the bundle plugin definition (see the [plugin definition](../../../docs/curate/ecr-bundle-details.md) for more information). These roles are created for the client specific to the microservice, e.g. `-conference-server`. The client name is injected as an environment variable into the plugin container, so the annotations noted above will work in both local and Kubernetes environments. -### Local versus Kubernetes Testing -This tutorial also makes use of the `internal` client configured in the microservice via the application.yml with roles manually created and assigned in Keycloak. In Kubernetes, Entando will automatically create client roles per the bundle plugin definition (see the plugin definition [here](../../../docs/curate/ecr-bundle-details.md) for more information). Those roles will be created for the client specific to the microservice itself, e.g. `-conference-server`. This client name will be injected as an environment variable into the plugin container itself so the annotations noted above will work both in local and Kubernetes environments. +#### Modify Security Checks for Kubernetes -The MFE authorization checks in the tutorial explicitly note the client id, e.g. `internal`, which won't work in Kubernetes. There are a couple options here: -1) Change the application.yml clientId under `security.oauth2.client.registration.oidc` to match the Kubernetes clientId. That's the most secure and allows the MFE checks to work the same in both local and Kubernetes environments. However, you not be be able to use the same clientId depending on how the microservice is deployed -2) An alternative is to broaden the MFE authorization check to look for a named role on any client. This could result in overlap with other clients but with appropriately named roles (e.g. prefixed by feature, e.g. `conference-admin`) this could be the most flexible option. This can be provided via a helper function, e.g. in `api/helpers.js`: +In this tutorial, the MFE authorization checks explicitly note the client ID, e.g. `internal`. The following options modify the checks to work in Kubernetes: + +1) Change the `application.yml` client ID under `security.oauth2.client.registration.oidc` to match the Kubernetes client ID. + + This is the most secure option and allows the MFE checks to work identically in both local and Kubernetes environments. However, you may not be be able to use the same clientId, depending on how the microservice is deployed. + +2) Broaden the MFE authorization check to look for a named role on any client. + + This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. prefixed by feature, e.g. `conference-admin`). It can be achieved via a helper function, e.g. in `api/helpers.js`, and results in a simpler role check: ``` -//Check if the authenticated user has the clientRole for any keycloak clients +// Add helper function +// Check if the authenticated user has the clientRole for any Keycloak clients export const hasKeycloakClientRole = clientRole => { if (getKeycloakToken()) { const { resourceAccess } = window.entando.keycloak; @@ -119,11 +204,10 @@ export const hasKeycloakClientRole = clientRole => { } return false; }; + +// Perform role check +const isAdmin = hasKeycloakClientRole('conference-admin'); ``` -This would result in a simpler role check: -``` - const isAdmin = hasKeycloakClientRole('conference-admin'); -``` - -### Debugging -In both local and Kubernetes environments, the default blueprint javascript will make a global variable available in the browser, e.g. `window.entando.keycloak`. Examining this variable can help diagnose issues with assigned roles and authorities. In some cases you may need to logout of Entando and re-authenticate in order to get the latest role assignments. \ No newline at end of file + +### Troubleshooting +In both local and Kubernetes environments, the default Blueprint Javascript provides a global variable in the browser, e.g. `window.entando.keycloak`. Examining this variable can help diagnose issues with assigned roles and authorities. In some cases, you may need to logout of Entando and reauthenticate for the latest role assignments to be applied. \ No newline at end of file From 4cb242a169376ae95108a11397bbb3c441a973fe Mon Sep 17 00:00:00 2001 From: Nathan Shaw Date: Tue, 23 Aug 2022 16:39:03 -0400 Subject: [PATCH 7/9] ENDOC-495 Apply past PR feedback --- .../next/tutorials/create/ms/add-access-controls.md | 10 +++++----- .../v7.0/tutorials/create/ms/add-access-controls.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index dc2fe152fd..5dedc517ff 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -69,11 +69,11 @@ The following commands must be run from your project directory. They leverage th ``` sh ent prj ext-keycloak start ``` -2. Start the microservice in a new shell +2. Start the microservice in another shell ``` sh ent prj be-test-run ``` -3. Start the tableWidget MFE +3. Start the tableWidget MFE in a third shell ``` sh ent prj fe-test-run ``` @@ -115,7 +115,7 @@ To grant access to the `getAllConferences` API: ### Step 7: Restrict the ability to delete Conferences -The `conference-user` role grants the admin user permission to delete Conferences. To restrict the delete method to the `conference-admin` role: +The `conference-admin` role should grant a user permission to delete Conferences. To restrict the delete method to the `conference-admin` role: 1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory 2. Open `ConferenceResource.java` @@ -147,7 +147,7 @@ The MFE UI can be updated to hide the delete button from a user without the `con const Actions = ({ item }) => showDelete ? ( ``` -4. Confirm that the delete icon is no longer visible in the MFE, which should have automatically reloaded +4. Confirm that the delete icon is no longer visible in the MFE. The MFE should have automatically reloaded to reflect the code changes. ### Step 9: Grant and verify delete permissions @@ -185,7 +185,7 @@ In this tutorial, the MFE authorization checks explicitly note the client ID, e 2) Broaden the MFE authorization check to look for a named role on any client. - This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. prefixed by feature, e.g. `conference-admin`). It can be achieved via a helper function, e.g. in `api/helpers.js`, and results in a simpler role check: + This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. with a bundle or feature prefix like `conference-` in `conference-admin`). It can be achieved via a helper function, e.g. `api/helpers.js`, and results in a simpler role check: ``` // Add helper function // Check if the authenticated user has the clientRole for any Keycloak clients diff --git a/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md b/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md index 5f14947324..3695404552 100644 --- a/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md @@ -69,11 +69,11 @@ The following commands must be run from your project directory. They leverage th ``` sh ent prj ext-keycloak start ``` -2. Start the microservice in a new shell +2. Start the microservice in another shell ``` sh ent prj be-test-run ``` -3. Start the tableWidget MFE +3. Start the tableWidget MFE in a third shell ``` sh ent prj fe-test-run ``` @@ -115,7 +115,7 @@ To grant access to the `getAllConferences` API: ### Step 7: Restrict the ability to delete Conferences -The `conference-user` role grants the admin user permission to delete Conferences. To restrict the delete method to the `conference-admin` role: +The `conference-admin` role should grant a user permission to delete Conferences. To restrict the delete method to the `conference-admin` role: 1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory 2. Open `ConferenceResource.java` @@ -147,7 +147,7 @@ The MFE UI can be updated to hide the delete button from a user without the `con const Actions = ({ item }) => showDelete ? ( ``` -4. Confirm that the delete icon is no longer visible in the MFE, which should have automatically reloaded +4. Confirm that the delete icon is no longer visible in the MFE. The MFE should have automatically reloaded to reflect the code changes. ### Step 9: Grant and verify delete permissions @@ -185,7 +185,7 @@ In this tutorial, the MFE authorization checks explicitly note the client ID, e 2) Broaden the MFE authorization check to look for a named role on any client. - This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. prefixed by feature, e.g. `conference-admin`). It can be achieved via a helper function, e.g. in `api/helpers.js`, and results in a simpler role check: + This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. with a bundle or feature prefix like `conference-` in `conference-admin`). It can be achieved via a helper function, e.g. `api/helpers.js`, and results in a simpler role check: ``` // Add helper function // Check if the authenticated user has the clientRole for any Keycloak clients From 184bb8d2ec5bcaf22e5bb8978c8b2471f67a11bf Mon Sep 17 00:00:00 2001 From: Nathan Shaw Date: Tue, 23 Aug 2022 16:41:00 -0400 Subject: [PATCH 8/9] ENDOC-495 Apply past PR feedback --- vuepress/docs/next/tutorials/create/ms/add-access-controls.md | 2 +- vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index 5dedc517ff..03d63e69a3 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -44,7 +44,7 @@ private String clientId; jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter(clientId)); ``` 6. Now modify - `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter`.java` to accept the clientId. Three changes are required. + `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter.java` to accept the clientId. Three changes are required. * Remove the @Component annotation on the class definition: ```java @Component diff --git a/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md b/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md index 3695404552..9f409bf6ee 100644 --- a/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md @@ -44,7 +44,7 @@ private String clientId; jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthorityConverter(clientId)); ``` 6. Now modify - `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter`.java` to accept the clientId. Three changes are required. + `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter.java` to accept the clientId. Three changes are required. * Remove the @Component annotation on the class definition: ```java @Component From e8ca74e28507239f18fa1a952364cfde8a2b5e56 Mon Sep 17 00:00:00 2001 From: Nathan Shaw Date: Wed, 24 Aug 2022 15:30:55 -0400 Subject: [PATCH 9/9] ENDOC-495 Clarify code blocks --- .../create/ms/add-access-controls.md | 22 +++++++++++-------- .../create/ms/add-access-controls.md | 22 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md index 03d63e69a3..1ddf5253ae 100644 --- a/vuepress/docs/next/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/next/tutorials/create/ms/add-access-controls.md @@ -20,12 +20,13 @@ The list of Conferences must be visible to only the `conference-user` and `confe 1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory 2. Open `ConferenceResource.java` 3. Add the following to the list of imports: -``` +```java import org.springframework.security.access.prepost.PreAuthorize; ``` -4. Modify the REST API `Conference:getAllConferences` method by preceding it with the annotation below: -``` +4. Modify the REST API `Conference:getAllConferences` method by preceding it with the @PreAuthorize annotation. Your method signature may be different depending on your blueprint selections. +```java{1} @PreAuthorize("hasAnyAuthority('conference-user','conference-admin')") + public List getAllConferences() ``` This confines use of the `getAllConferences` method to users who are assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. @@ -45,13 +46,15 @@ jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAutho ``` 6. Now modify `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter.java` to accept the clientId. Three changes are required. - * Remove the @Component annotation on the class definition: - ```java + * Remove the @Component annotation on the class definition + ```java{1} @Component +public class JwtGrantedAuthorityConverter implements Converter> { ``` * Remove the @Value annotation on the clientId field -```java +```java{1} @Value("${spring.security.oauth2.client.registration.oidc.client-id}") +private String clientId; ``` * Modify the constructor to accept the clientId ```java @@ -120,8 +123,9 @@ The `conference-admin` role should grant a user permission to delete Conferences 1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory 2. Open `ConferenceResource.java` 3. Modify the `deleteConference` method by preceding it with the following annotation: -``` +```java{1} @PreAuthorize("hasAuthority('conference-admin')") + public ResponseEntity deleteConference(@PathVariable Long id) ``` To verify that a user without the `conference-admin` role is unable to call the delete API: @@ -140,7 +144,7 @@ The MFE UI can be updated to hide the delete button from a user without the `con 1. Go to the `ui/widgets/conference/tableWidget/src/components` directory 2. Open `ConferenceTableContainer.js` 3. Replace the `onDelete` logic with an additional user permission: -``` +```javascript const isAdmin = (keycloak && keycloak.authenticated) ? keycloak.hasResourceRole("conference-admin", "internal"): false; const showDelete = onDelete && isAdmin; @@ -186,7 +190,7 @@ In this tutorial, the MFE authorization checks explicitly note the client ID, e 2) Broaden the MFE authorization check to look for a named role on any client. This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. with a bundle or feature prefix like `conference-` in `conference-admin`). It can be achieved via a helper function, e.g. `api/helpers.js`, and results in a simpler role check: -``` +```javascript // Add helper function // Check if the authenticated user has the clientRole for any Keycloak clients export const hasKeycloakClientRole = clientRole => { diff --git a/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md b/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md index 9f409bf6ee..4b9fbb2da4 100644 --- a/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md +++ b/vuepress/docs/v7.0/tutorials/create/ms/add-access-controls.md @@ -20,12 +20,13 @@ The list of Conferences must be visible to only the `conference-user` and `confe 1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory 2. Open `ConferenceResource.java` 3. Add the following to the list of imports: -``` +```java import org.springframework.security.access.prepost.PreAuthorize; ``` -4. Modify the REST API `Conference:getAllConferences` method by preceding it with the annotation below: -``` +4. Modify the REST API `Conference:getAllConferences` method by preceding it with the @PreAuthorize annotation. Your method signature may be different depending on your blueprint selections. +```java{1} @PreAuthorize("hasAnyAuthority('conference-user','conference-admin')") + public List getAllConferences() ``` This confines use of the `getAllConferences` method to users who are assigned either the `conference-user` or the `conference-admin` role on the Keycloak client configured for the microservice. @@ -45,13 +46,15 @@ jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAutho ``` 6. Now modify `src/main/java/com/mycompany/myapp/security/oauth2/JwtGrantedAuthorityConverter.java` to accept the clientId. Three changes are required. -* Remove the @Component annotation on the class definition: - ```java +* Remove the @Component annotation on the class definition + ```java{1} @Component +public class JwtGrantedAuthorityConverter implements Converter> { ``` * Remove the @Value annotation on the clientId field -```java +```java{1} @Value("${spring.security.oauth2.client.registration.oidc.client-id}") +private String clientId; ``` * Modify the constructor to accept the clientId ```java @@ -120,8 +123,9 @@ The `conference-admin` role should grant a user permission to delete Conferences 1. Go to the `src/main/java/com/YOUR-ORG/YOUR-NAME/web/rest` directory 2. Open `ConferenceResource.java` 3. Modify the `deleteConference` method by preceding it with the following annotation: -``` +```java{1} @PreAuthorize("hasAuthority('conference-admin')") + public ResponseEntity deleteConference(@PathVariable Long id) ``` To verify that a user without the `conference-admin` role is unable to call the delete API: @@ -140,7 +144,7 @@ The MFE UI can be updated to hide the delete button from a user without the `con 1. Go to the `ui/widgets/conference/tableWidget/src/components` directory 2. Open `ConferenceTableContainer.js` 3. Replace the `onDelete` logic with an additional user permission: -``` +```javascript const isAdmin = (keycloak && keycloak.authenticated) ? keycloak.hasResourceRole("conference-admin", "internal"): false; const showDelete = onDelete && isAdmin; @@ -186,7 +190,7 @@ In this tutorial, the MFE authorization checks explicitly note the client ID, e 2) Broaden the MFE authorization check to look for a named role on any client. This could result in overlap with other clients, but this is the most flexible option when using appropriately named roles (e.g. with a bundle or feature prefix like `conference-` in `conference-admin`). It can be achieved via a helper function, e.g. `api/helpers.js`, and results in a simpler role check: -``` +```javascript // Add helper function // Check if the authenticated user has the clientRole for any Keycloak clients export const hasKeycloakClientRole = clientRole => {