Use POST request to force unsubscribe by email clients
What does this MR do and why?
This MR fixes an issue when an external participant on a Service Desk issue gets unsubscribed immediately after they received a Service Desk notification email. See #466167 (closed)
This issue occurs when customers have scanners in place that scan and visit URLs from email headers. We store an unsubscribe link in the email header that allows email clients to automatically unsubscribe users from email lists. RFC8058 suggests using a POST request for these URLs because it's a destructive operation when the URL is accessed via a GET request.
GitLab right now uses a GET request with an additional query param force=true. Without this additional parameter users see a confirmation page (if not logged in).
This MR allows POST requests to the unsubscribe endpoint and removes the force=true parameter from the header URLs. That means the following:
- If you access the unsubscribe URL in your browser --> confirmation page because it
sGET`👍 - If you confirm, we call the same URL with
&force=trueappended. This directly unsubscribes. Nice because it's stillGET - The automatic unsubscribe header now doesn't have
&force=trueany more, so it doesn't unsubscribe viaGET(aka just fetch the url).👍 - If you access the URL via
POSTit directly unsubscribes. This is what we want and what is standard compliant.👍 - Because we only add new functionality it's also backward compatible without broken URLs etc. and doesn't need any change management.
👍
spec/controllers/sent_notifications_controller_spec.rb with let_it_be to reduce execution time and pulls out a few examples so we can better reuse them for the new POST action.
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Screenshots or screen recordings
Screenshots are required for UI changes, and strongly recommended for all other merge requests.
How to set up and validate locally
Numbered steps to set up and validate the change are strongly suggested.
- Configure
incoming_emailwith dummy settings so you can use Service Desk. Ingitlab.ymlinconfig/gitlab.ymladd under thedevelopmentkey (approx. line 893) the following lines and restart GDKgdk restart.incoming_email: enabled: true address: "incoming+%{key}@example.com" - There are a few ways to check this. The goal is to get a reply key from
SentNotification:- Go to the console and select the reply key from the last
SentNotification. Make sure that the user/external participant is still subscribed to the issue. If you didn't do anything with this before, you should be ready to go.SentNotification.last.reply_key # Construct the url "http://127.0.0.1:3000/-/sent_notifications/#{SentNotification.last.reply_key}/unsubscribe" - Create a new issue in a project of your choice. Then add a comment with the following content
Now open letter opener (http://127.0.0.1:3000/rails/letter_opener) and you should find a new participant email to default@example.com. If they don't show up
/add_email test@example.com😟 a gdk restart might help. In my installation some background jobs are stuck until I restart. Maybe that helps👍 Then copy the unsubscribe link.
- Go to the console and select the reply key from the last
- In a new private browser tab!!! (it automatically unsubscribes if you browse to the unsubscribe URL if you're logged in) browse to the unsubscribe URL. You should see the confirmation dialog. You can close that tab now.
- Perform a
POSTrequest to the same URL for example usingcurlThe response body should be a redirect to the sign in page (success):curl -X POST "http://127.0.0.1:3000/-/sent_notifications/[KEY]/unsubscribe"<html><body>You are being <a href="http://127.0.0.1:3000/users/sign_in">redirected</a>.</body></html>%
Related to #466167 (closed)