Commit d2efe6d9 authored by Max Woolf's avatar Max Woolf

Send notification emails on credential revocation

When an administrator revokes or deletes
a PAT or SSH key, the owner of the
credential is notified.
parent 378536cc
...@@ -47,3 +47,32 @@ If you see a **Revoke** button, you can revoke that user's PAT. Whether you see ...@@ -47,3 +47,32 @@ If you see a **Revoke** button, you can revoke that user's PAT. Whether you see
You can **Delete** a user's SSH key by navigating to the credentials inventory's SSH Keys tab. You can **Delete** a user's SSH key by navigating to the credentials inventory's SSH Keys tab.
![Credentials inventory page - SSH keys](img/credentials_inventory_ssh_keys_v13_5.png) ![Credentials inventory page - SSH keys](img/credentials_inventory_ssh_keys_v13_5.png)
## Revocation or deletion notification
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250354) in GitLab 13.6.
> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
> - It's disabled on GitLab.com.
> - It's not recommended for production use.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-revocation-or-deletion-notification).
CAUTION: **Warning:**
This feature might not be available to you. Check the **version history** note above for details.
### Enable or disable revocation or deletion notification **(ULTIMATE ONLY)**
Revocation or deletion notification is under development and not ready for production use. It is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it.
To enable it:
```ruby
Feature.enable(:credentials_inventory_revocation_emails)
```
To disable it:
```ruby
Feature.disable(:credentials_inventory_revocation_emails)
```
...@@ -19,6 +19,7 @@ module CredentialsInventoryActions ...@@ -19,6 +19,7 @@ module CredentialsInventoryActions
alert = if key.present? alert = if key.present?
if Keys::DestroyService.new(current_user).execute(key) if Keys::DestroyService.new(current_user).execute(key)
notify_deleted_or_revoked_credential(key)
_('User key was successfully removed.') _('User key was successfully removed.')
else else
_('Failed to remove user key.') _('Failed to remove user key.')
...@@ -33,7 +34,12 @@ module CredentialsInventoryActions ...@@ -33,7 +34,12 @@ module CredentialsInventoryActions
def revoke def revoke
personal_access_token = PersonalAccessTokensFinder.new({ user: users, impersonation: false }, current_user).find(params[:id]) personal_access_token = PersonalAccessTokensFinder.new({ user: users, impersonation: false }, current_user).find(params[:id])
service = PersonalAccessTokens::RevokeService.new(current_user, token: personal_access_token).execute service = PersonalAccessTokens::RevokeService.new(current_user, token: personal_access_token).execute
service.success? ? flash[:notice] = service.message : flash[:alert] = service.message if service.success?
flash[:notice] = service.message
notify_deleted_or_revoked_credential(personal_access_token)
else
flash[:alert] = service.message
end
redirect_to credentials_inventory_path(page: params[:page]) redirect_to credentials_inventory_path(page: params[:page])
end end
...@@ -48,6 +54,16 @@ module CredentialsInventoryActions ...@@ -48,6 +54,16 @@ module CredentialsInventoryActions
end end
end end
def notify_deleted_or_revoked_credential(credential)
return unless Feature.enabled?(:credentials_inventory_revocation_emails, credential.user)
if credential.is_a?(Key)
CredentialsInventoryMailer.ssh_key_deleted_email(key: credential, deleted_by: current_user).deliver_later
elsif credential.is_a?(PersonalAccessToken)
CredentialsInventoryMailer.personal_access_token_revoked_email(token: credential, revoked_by: current_user).deliver_later
end
end
def users def users
raise NotImplementedError, "#{self.class} does not implement #{__method__}" raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end end
......
---
name: credentials_inventory_revocation_emails
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46033
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/271577
type: development
group: group::compliance
default_enabled: false
...@@ -164,6 +164,24 @@ RSpec.describe Admin::CredentialsController do ...@@ -164,6 +164,24 @@ RSpec.describe Admin::CredentialsController do
expect(response).to redirect_to(admin_credentials_path) expect(response).to redirect_to(admin_credentials_path)
expect(flash[:notice]).to eql 'Revoked personal access token %{personal_access_token_name}!' % { personal_access_token_name: personal_access_token.name } expect(flash[:notice]).to eql 'Revoked personal access token %{personal_access_token_name}!' % { personal_access_token_name: personal_access_token.name }
end end
it 'informs the token owner' do
expect(CredentialsInventoryMailer).to receive_message_chain(:personal_access_token_revoked_email, :deliver_later)
put :revoke, params: { id: personal_access_token.id }
end
context 'when credentials_inventory_revocation_emails flag is disabled' do
before do
stub_feature_flags(credentials_inventory_revocation_emails: false)
end
it 'does not inform the token owner' do
expect do
put :revoke, params: { id: personal_access_token.id }
end.not_to change { ActionMailer::Base.deliveries.size }
end
end
end end
end end
end end
......
...@@ -33,6 +33,24 @@ RSpec.shared_examples_for 'credentials inventory controller delete SSH key' do | ...@@ -33,6 +33,24 @@ RSpec.shared_examples_for 'credentials inventory controller delete SSH key' do |
expect(response).to redirect_to(credentials_path) expect(response).to redirect_to(credentials_path)
expect(flash[:notice]).to eql 'User key was successfully removed.' expect(flash[:notice]).to eql 'User key was successfully removed.'
end end
it 'notifies the key owner' do
expect(CredentialsInventoryMailer).to receive_message_chain(:ssh_key_deleted_email, :deliver_later)
subject
end
context 'when credentials_inventory_revocation_emails is disabled' do
before do
stub_feature_flags(credentials_inventory_revocation_emails: false)
end
it 'does not notify the key owner' do
expect(CredentialsInventoryMailer).not_to receive(:ssh_key_deleted_email)
subject
end
end
end end
context 'and it fails to remove the key' do context 'and it fails to remove the key' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment