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:

  1. If you access the unsubscribe URL in your browser --> confirmation page because its GET` 👍
  2. If you confirm, we call the same URL with &force=true appended. This directly unsubscribes. Nice because it's still GET
  3. The automatic unsubscribe header now doesn't have &force=true any more, so it doesn't unsubscribe via GET (aka just fetch the url).👍
  4. If you access the URL via POST it directly unsubscribes. This is what we want and what is standard compliant. 👍
  5. Because we only add new functionality it's also backward compatible without broken URLs etc. and doesn't need any change management. 👍

This MR doesn't have a documentation change, because we don't have documentation on unsubscribe behavior in the docs right now. We'll add documentation in a follow up MR.

This MR also contains spec refactors in 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.

🚫 backend only changes.

How to set up and validate locally

Numbered steps to set up and validate the change are strongly suggested.

  1. Configure incoming_email with dummy settings so you can use Service Desk. In gitlab.yml in config/gitlab.yml add under the development key (approx. line 893) the following lines and restart GDK gdk restart.
      incoming_email:
        enabled: true
        address: "incoming+%{key}@example.com"
  2. There are a few ways to check this. The goal is to get a reply key from SentNotification:
    1. 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"
    2. Create a new issue in a project of your choice. Then add a comment with the following content
      /add_email test@example.com
      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 😟 a gdk restart might help. In my installation some background jobs are stuck until I restart. Maybe that helps 👍 Then copy the unsubscribe link.
  3. 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.
  4. Perform a POST request to the same URL for example using curl
    curl -X POST "http://127.0.0.1:3000/-/sent_notifications/[KEY]/unsubscribe" 
    The response body should be a redirect to the sign in page (success):
    <html><body>You are being <a href="http://127.0.0.1:3000/users/sign_in">redirected</a>.</body></html>%  

Related to #466167 (closed)

Edited by Marc Saleiko

Merge request reports

Loading