Repository: getredash/redash

Issue:
Pending invitation links broken in latest version

<!--

We use GitHub only for bug reports 🐛

Anything else should be posted to https://discuss.redash.io 👫

🚨For support, help & questions use https://discuss.redash.io/c/support
💡For feature requests & ideas use https://discuss.redash.io/c/feature-requests

**Found a security vulnerability?** Please email security@redash.io to report any security vulnerabilities. We will acknowledge receipt of your vulnerability and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. If you want to encrypt your disclosure email, you can use this PGP key.

-->

### Issue Summary

It looks like the change in #3261 doesn't handle the case of existing users that haven't accepted their invitations yet, so users trying to sign up using invitations created in a previous version incorrectly get the "This invitation has already been accepted" message. https://github.com/getredash/redash/pull/3229/files#diff-a0c5448134fdb627ae48d25bad76393fR40

I was able to fix this in our org with:

```sql
UPDATE users
SET details = '{"is_invitation_pending": true}'::json
WHERE password_hash IS NULL
```

Maybe this case should be handled in the migration here? https://github.com/getredash/redash/blob/master/migrations/versions/e7f8a917aa8e_add_user_details_json_column.py

### Steps to Reproduce

1. Send invitation from a version prior to #3229 and #3261 
2. Upgrade to current master
3. Try to access the invitation link

Any other info e.g. Why do you consider this to be a bug? What did you expect to happen instead?

### Technical details:

* Redash Version:
* Browser/OS:
* How did you install Redash:



Discussion:
Good catch, @chang! We can't do a migration in this case, because prior to this change there was no information on whether the user accepted the invitation or not. 

I think the solution can be to assume the invitation is pending if there is no `is_invitation_pending` key in the `details` property. To avoid code changes, I would rename the db property to `_is_invitation_pending`, and have a class property that does this check.

Resolving patch:
diff --git a/redash/handlers/authentication.py b/redash/handlers/authentication.py
--- a/redash/handlers/authentication.py
+++ b/redash/handlers/authentication.py
@@ -38,7 +38,7 @@ def render_token_login_page(template, org_slug, token):
         return render_template("error.html",
                                error_message="Your invite link has expired. Please ask for a new one."), 400
 
-    if not user.is_invitation_pending:
+    if user.details.get('is_invitation_pending') is False:
         return render_template("error.html",
                                error_message=("This invitation has already been accepted. "
                                               "Please try resetting your password instead.")), 400


Test patch:
diff --git a/tests/handlers/test_authentication.py b/tests/handlers/test_authentication.py
--- a/tests/handlers/test_authentication.py
+++ b/tests/handlers/test_authentication.py
@@ -50,6 +50,12 @@ def test_bad_token(self):
         response = self.post_request('/invite/{}'.format('jdsnfkjdsnfkj'), data={'password': '1234'}, org=self.factory.org)
         self.assertEqual(response.status_code, 400)
 
+    def test_user_invited_before_invitation_pending_check(self):
+        user = self.factory.create_user(details={})
+        token = invite_token(user)
+        response = self.post_request('/invite/{}'.format(token), data={'password': 'test1234'}, org=self.factory.org)
+        self.assertEqual(response.status_code, 302)
+
     def test_already_active_user(self):
         token = invite_token(self.factory.user)
         self.post_request('/invite/{}'.format(token), data={'password': 'test1234'}, org=self.factory.org)