Skip to content
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

Clean up SCIM-invited users with expired invitation #1264

Merged
merged 51 commits into from
Dec 21, 2020

Conversation

smatting
Copy link
Contributor

@smatting smatting commented Nov 30, 2020

Implements the clean up job discussed in #1238

https://wearezeta.atlassian.net/browse/SQSERVICES-26

@smatting smatting marked this pull request as ready for review December 1, 2020 14:00
@fisx
Copy link
Contributor

fisx commented Dec 1, 2020

Copy link
Contributor

@fisx fisx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are no tests whatsoever. Not sure how to test this, and where, perhaps we can think about this together.

Sorry, lots of ideas how to change this. Do you want to go through them together?

libs/types-common/src/Data/Misc.hs Outdated Show resolved Hide resolved
services/brig/schema/src/Main.hs Outdated Show resolved Hide resolved
services/brig/schema/src/V62_users_pending_activation.hs Outdated Show resolved Hide resolved
services/brig/schema/src/V62_users_pending_activation.hs Outdated Show resolved Hide resolved
services/brig/src/Brig/Data/PendingActivation.hs Outdated Show resolved Hide resolved
else pure users'

safeForever :: (MonadIO m, MonadLogger m, MonadCatch m) => String -> m () -> m ()
safeForever funName action =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is duplicating existing code. Can we move the old function somewhere from where we can call it here as well? Or is it awkward to generalize? (Not that important.)

Copy link
Contributor Author

@smatting smatting Dec 16, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's already duplicated 3 times. I created a separate issue for this https://wearezeta.atlassian.net/browse/SQSERVICES-146

services/brig/src/Brig/Run.hs Outdated Show resolved Hide resolved
services/brig/src/Brig/Run.hs Outdated Show resolved Hide resolved
services/brig/src/Brig/Run.hs Outdated Show resolved Hide resolved
isPendingInvitation <- (Just PendingInvitation ==) <$> lookupStatus uid
invExpired <- isNothing <$> Data.lookupInvitation tid (coerce uid)
when (isPendingInvitation && invExpired) $ do
API.deleteUserNoVerify uid
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should keep an eye on this.

I suggest we:

  • add a function deleteUsersNoVerify that deletes many users
  • we can implement it for now using mapM_
  • but we should call it only once per page of PendingActivationExpiration
  • and keep a metric that tells us how often this is called
  • ideally, we should also have a metric that tells us how much time & space deletion takes, but that may be too much work for now.

Copy link
Contributor Author

@smatting smatting Dec 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid scheduling too many deletions too fast, I added a throttling delay, which will schedule at most 2 items per second (170k per day).

Implemented suggestions 1-4

services/brig/src/Brig/Run.hs Outdated Show resolved Hide resolved
@smatting smatting force-pushed the smatting/expired-invitations branch from f744043 to 3e25585 Compare December 2, 2020 18:50
@smatting smatting force-pushed the smatting/expired-invitations branch from d978ef4 to d6b98fe Compare December 2, 2020 19:59
@jschaul
Copy link
Member

jschaul commented Dec 15, 2020

one more thing: what happens if a user gets deleted by means other than expiring invitation? it will still have an entry in the new table, right? will the gc loop choke on that entry? solution may be to modify the async delete-user routine to also handle the new table.

I think use deletions are idempotent; so you can attempt to delete the same user over and over again with the same result. It might be nice to (if not already done, I didn't look at the code, just replying to the previous comment), upon regular user deletions, as part of the deletion onEvent flow also delete the entry in this new expiry-tracking table (which will be a no-op if no entry under that key exists)

@smatting
Copy link
Contributor Author

If the GC loop cannot find a user then isPendingInvitation will be False:

isPendingInvitation <- (Just PendingInvitation ==) <$> API.lookupStatus uid

The user then skips deletion and the entry is removed from the expiry table.

Currently there are no calls to remove an entry from the expiry table except for the loop. The GC loop removes entries from the expiry table by itself. I have slight prefrence for not adding it as a hook to the user deletion, because this creates two call sites that remove entries from the table, one of which is not even strictly necessary.

I have another clarification question for @fisx : Is it possible that spar creates a second invitation for a user after the first invitation is expired? If so then I need to implement this: "every user has at most on entry in the expiry table, which will be overwritten with every invitation".

@jschaul
Copy link
Member

jschaul commented Dec 15, 2020

then I need to implement this: "every user has at most on entry in the expiry table, which will be overwritten with every invitation".

If needed, this implementation can be achieved easily with the following:

        (
          user            uuid
        , expires_at      timestamp
-        , primary key (user, expires_at)
+        , primary key (user)
        )       

I believe at this point you don't need to have multiple entries for the same user, so you don't need a composite primary key.

@smatting
Copy link
Contributor Author

If needed, this implementation can be achieved easily with the following:

        (
          user            uuid
        , expires_at      timestamp
-        , primary key (user, expires_at)
+        , primary key (user)
        )       

Thanks! Applied.

@smatting smatting requested a review from fisx December 16, 2020 16:46
@@ -38,7 +38,6 @@ data UserPendingActivation = UserPendingActivation
}
deriving stock (Eq, Show, Ord)

-- | Note: Call this function only after an invitation for the user has been created
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, nice. Didn't see that.

services/brig/src/Brig/Data/UserPendingActivation.hs Outdated Show resolved Hide resolved
Copy link
Contributor

@fisx fisx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've committed one more change, pls check whether this makes sense, and if you agree :shipit:

@fisx fisx merged commit 84aaf10 into develop Dec 21, 2020
@fisx fisx deleted the smatting/expired-invitations branch December 21, 2020 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants