-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hide retired apps on most pages #2446
Conversation
41aab71
to
4946651
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all looks good to me @floehopper - thanks for the detailed commit messages and easy-to-follow history. I've made one small suggestion that shouldn't block merging this.
This column was already defaulting to FALSE and I can't see any reason why we'd want to allow NULL values; they'd just confuse matters. This migration replaces any NULL values with FALSE and prevents the column from ever being set to NULL in the future.
We already have a `not_retired` scope and there's at least a couple of places where using a `retired` scope would be an improvement.
Applications are listed in two tabs on the apps index page. This checks that non-retired apps are displayed in the "active" tab and retired apps are displayed in the "retired" tab. I'm planning to make a change in this area and I wanted to ensure there was some test coverage in place.
I'm planning to make a change in this area and I wanted to ensure there was some test coverage in place.
I'm planning to make a change in this area and I wanted to ensure there was some test coverage in place.
The "scopes" context wasn't adding much and not all tests for scopes were inside it anyway! This diff is best viewed with the --ignore-all-space option, because of the indentation changes.
I'm about to add a default scope which will make use of the not_retired scope. I think re-ordering the test contexts like this will mean the Doorkeeper::ApplicationTest makes more sense.
In `Doorkeeper::Application` class, c.f. `Doorkeeper::AccessToken` & `Doorkeeper::AccessGrant`. This is a bit orthogonal to the other changes in this PR, but I'm making changes in this area of the code and I was finding the verbose comments distracting!
To make it clearer that this controller has an `edit` action which renders the view containing the form for updating an application.
I'm about to add a default scope for `Doorkeeper::Application` which will mean that retired applications are not accessible from the UI by default. I think this is one of the places in the app where we *do* still want to have access to retired apps, so I'm adding this test coverage in advance of adding the default scope to make sure I don't break anything.
I'm about to make some changes in this area and I think doing this first will make it easier to see what's going on.
Since the application factory sets a name by default and the name isn't referenced in the rest of the test, there's no need to set it here and I was finding it distracting when reading the tests.
I'm planning to add a default scope to `Doorkeeper::Application` which will make retired apps unavailable by default. I suspect that will change the behaviour captured in this new test and so having the test in place first will make it clearer how the behaviour has changed in the subsequent commit(s).
Retiring an app is effectively a "soft delete" and so retired apps should only appear in a very few pages in the UI, i.e. pages where the user explicitly wants to view retired apps and pages where the user want to edit a retired app, e.g. to un-retired it. My motivation for doing this is from this Trello card [1] where we want to reduce the number of apps displayed on various pages to only include the useful ones. That card is actually about hiding API-only apps, but it makes sense to me to hide retired apps from most pages as a first step. Changing the default scope like this broke the `User#grant_application_permissions` method, because `User#grant_permission` ended up trying to create a `UserApplicationPermission`, but failed because the application was `nil`. I've chosen to "fix" this method by changing its behaviour so it no longer grants permissions for a *retired* app. I did consider raising an exception if the app was retired, but in the end I decided that was a bit drastic. I've made use of `Doorkeeper::Application.unscoped` in the `DoorkeeperApplicationsController` and its view templates to ensure that the user can still view and edit retired apps. It's possible that this commit will break some untested behaviour. However, we should find out about that relatively quickly and I think the benefits outweigh the risks. Note that I've had to tweak the `after :create` hook in the `application` factory to avoid a validation error in a test. I _think_ this is because the application in the test is retired and when the hook is executed `app` doesn't have an `id` which somehow results in the relatively recently added presence validation for `Doorkeeper::Application#application` failing. I only actually needed to change the code in one of the loops within the hook to fix the test, but it makes sense to me that I should change all of them at the same time. I haven't investigated this very thoroughly, because (a) I think the new version of the code is more idiomatic Rails; and (b) I really dislike these FactoryBot hooks and I hope to get rid of them sometime soon! [1]: https://trello.com/c/kvmb5OHO
Now that `not_retired` is included in the default scope, there's no need to repeat it here. I'm confident that this scope has sufficient test coverage in `Doorkeeper::ApplicationTest` to give me confidence that this should not have broken anything.
Now that `not_retired` is included in the default scope, there's no need to repeat it here. I'm confident that this scope has sufficient test coverage in `DoorkeeperApplicationsControllerTest` to give me confidence that this should not have broken anything.
Now that `not_retired` is included in the default scope, there's no need to repeat it here. I'm confident that this scope has sufficient test coverage in `BatchInvitationPermissionsControllerTest` to give me confidence that this should not have broken anything.
Now that `not_retired` is included in the default scope, there's no need to repeat it here. I'm confident that this scope has sufficient test coverage in `AccountApplicationsTest` to give me confidence that this should not have broken anything.
Now that `not_retired` is included in the default scope, there's no need to repeat it here. I'm confident that this scope has sufficient test coverage in `Account::PermissionsControllerTest` to give me confidence that this should not have broken anything.
To reduce duplication.
Now that `not_retired` is included in the default scope, there's no need to repeat it here. I'm confident that this scope has sufficient test coverage in `Account::SigninPermissionsControllerTest` to give me confidence that this should not have broken anything.
Now that `not_retired` is included in the default scope, there's no need to repeat it here. I'm confident that this scope has sufficient test coverage in `InvitationsControllerTest` to give me confidence that this should not have broken anything.
I'm about to make a change to this, so I wanted to add some test coverage first.
I'm about to make some changes to `User#application_permissions` which will introduce a join onto the `oauth_applications` table which also has a `name` column thus making this change necessary.
We don't want to display a user's permissions for retired apps on any page and so to ensure that I've introduced this `joins(:application)` scope to the `User#application_permissions` `has_many` association so that it picks up the default scope on `Doorkeeper::Application`, i.e. `not_retired`. I don't think the `joins` scope itself will change which `UserApplicationPermission` records are returned, because `user_application_permissions.application_id` has a `NOT NULL` and a foreign key constraint and `UserApplicationPermission#application` has a `presence` validation on it. The `joins` scope just serves to include the default scope on `Doorkeeper::Application` in the query.
I'm about to make a change to this, so I wanted to add some test coverage first.
We don't want to display a user's permissions for retired apps on any page and so to ensure that I've introduced this `joins(:application)` scope to the `User#supported_permissions` `has_many` association so that it picks up the default scope on `Doorkeeper::Application`, i.e. `not_retired`. I don't think the `joins` scope itself will change which `SupportedPermission` records are returned, because `supported_permissions.application_id` has a `NOT NULL` and a foreign key constraint and `SupportedPermission#application` has a `presence` validation on it. The `joins` scope just serves to include the default scope on `Doorkeeper::Application` in the query.
Make it clear that retiring an application will hide it from most pages; not just the dashboard.
4946651
to
2bb738c
Compare
Thanks, @chrislo! |
The behaviour of this rake task was changed when we added `not_retired` to the default scope on `Doorkeeper::Application` in this commit [1] in #2446. Since then it has no longer synced app secrets for retired apps; instead it will raise an exception. This behaviour makes sense to me from a security point-of-view and I have confirmed that (even after the recent spate of app retirements) the Signon app still includes all the apps listed in `charts/app-config/templates/signon-secrets-sync-configmap.yaml` [2] which are used in the `sync-app-secrets-to-k8s` cron task e.g. in integration [3]. This means that despite the change in behaviour we should *not* see any exceptions. And if we do start seeing exceptions that probably means the list of apps in `signon-secrets-sync-configmap.yaml` needs to be updated. [1]: 9a9cd02 [2]: https://github.com/alphagov/govuk-helm-charts/blob/4c540acfa83899f0d73f9b06d480c712045cfc5f/charts/app-config/templates/signon-secrets-sync-configmap.yaml#L6-L42 [3]: https://github.com/alphagov/govuk-helm-charts/blob/4c540acfa83899f0d73f9b06d480c712045cfc5f/charts/app-config/values-integration.yaml#L2384-L2387
This covers the scenario where an app is retired *after* the job is enqueued but *before* it is executed. The behaviour being tested was actually introduced when we added the `not_retired` scope to the default scope for `Doorkeeper::Application` in this commit [1] in #2446. This is because both jobs call `Doorkeeper::Application.find_by` in their `#perform` methods which will include the default scope and so if the app is retired will return `nil` and be caught by the guard condition. [1]: 9a9cd02
This covers the scenario where an app is retired *after* the job is enqueued but *before* it is executed. The behaviour being tested was actually introduced when we added the `not_retired` scope to the default scope for `Doorkeeper::Application` in this commit [1] in #2446. This is because both jobs call `Doorkeeper::Application.find_by` in their `#perform` methods which will include the default scope and so if the app is retired will return `nil` and be caught by the guard condition. [1]: 9a9cd02
The behaviour of this rake task was changed when we added `not_retired` to the default scope on `Doorkeeper::Application` in this commit [1] in #2446. Since then it has no longer synced app secrets for retired apps; instead it will raise an exception. This behaviour makes sense to me from a security point-of-view and I have confirmed that (even after the recent spate of app retirements) the Signon app still includes all the apps listed in `charts/app-config/templates/signon-secrets-sync-configmap.yaml` [2] which are used in the `sync-app-secrets-to-k8s` cron task e.g. in integration [3]. This means that despite the change in behaviour we should *not* see any exceptions. And if we do start seeing exceptions that probably means the list of apps in `signon-secrets-sync-configmap.yaml` needs to be updated. [1]: 9a9cd02 [2]: https://github.com/alphagov/govuk-helm-charts/blob/4c540acfa83899f0d73f9b06d480c712045cfc5f/charts/app-config/templates/signon-secrets-sync-configmap.yaml#L6-L42 [3]: https://github.com/alphagov/govuk-helm-charts/blob/4c540acfa83899f0d73f9b06d480c712045cfc5f/charts/app-config/values-integration.yaml#L2384-L2387
This covers the scenario where an app is retired *after* the job is enqueued but *before* it is executed. The behaviour being tested was actually introduced when we added the `not_retired` scope to the default scope for `Doorkeeper::Application` in this commit [1] in #2446. This is because both jobs call `Doorkeeper::Application.find_by` in their `#perform` methods which will include the default scope and so if the app is retired will return `nil` and be caught by the guard condition. [1]: 9a9cd02
Trello: https://trello.com/c/kvmb5OHO
While the Trello card is about hiding API-only apps, the overall motivation is to reduce the number of apps on various permissions pages and so I thought a good first step would be to exclude permissions relating to retired apps.
This adds a condition to the default scope on the
Doorkeeper::Application
model to only includenot_retired
applications in any query by default. It also incorporates this default scope into thehas_many :application_permissions
&has_many :supported_permissions
associations on theUser
model. While using a default scope is perhaps a bit controversial, I think the notion of retiring an application is effectively a "soft delete" and this seems like a good use case for a default scope, not least because it should make it less likely a retired app or its permissions show up on any pages added in the future.All this should mean that retired apps will only be listed in the following places:
Doorkeeper::Application
model to thehas_many :authorisations
association on theUser
model. I've decided to hold off on that for now (a) to reduce the scope of this PR; and (b) because 2nd line might still get alerts for expired tokens associated with retired apps and I want them to be able to still be able to access the UI to revoke/regenerate the token.Retired apps should no longer appear on any pages where a user's permissions are being added/removed/changed.
The most significant commits are:
not_retired
toDoorkeeper::Application
default scopeUser#application_permissions
User#supported_permissions
The other commits are mostly adding test coverage, removing redundant code, or just generally tidying up.
I haven't yet removed the logic to wrap application names in
<del>
tags to strike-through the name, because I want to double-check no retired apps end up appearing anywhere in the integration or production environments before I make that change.Joseph has recommended that we publicise this change in the
#govuk-publishing
Slack channel before we deploy it.