def do_get_invites_controlled_by_user( user_profile: UserProfile) -> List[Dict[str, Any]]: """ Returns a list of dicts representing invitations that can be controlled by user_profile. This isn't necessarily the same as all the invitations generated by the user, as administrators can control also invitations that they did not themselves create. """ if user_profile.is_realm_admin: prereg_users = filter_to_valid_prereg_users( PreregistrationUser.objects.filter( referred_by__realm=user_profile.realm)) else: prereg_users = filter_to_valid_prereg_users( PreregistrationUser.objects.filter(referred_by=user_profile)) invites = [] for invitee in prereg_users: assert invitee.referred_by is not None invites.append( dict( email=invitee.email, invited_by_user_id=invitee.referred_by.id, invited=datetime_to_timestamp(invitee.invited_at), expiry_date=get_invitation_expiry_date( invitee.confirmation.get()), id=invitee.id, invited_as=invitee.invited_as, is_multiuse=False, )) if not user_profile.is_realm_admin: # We do not return multiuse invites to non-admin users. return invites multiuse_confirmation_objs = Confirmation.objects.filter( realm=user_profile.realm, type=Confirmation.MULTIUSE_INVITE).filter( Q(expiry_date__gte=timezone_now()) | Q(expiry_date=None)) for confirmation_obj in multiuse_confirmation_objs: invite = confirmation_obj.content_object assert invite is not None invites.append( dict( invited_by_user_id=invite.referred_by.id, invited=datetime_to_timestamp(confirmation_obj.date_sent), expiry_date=get_invitation_expiry_date(confirmation_obj), id=invite.id, link_url=confirmation_url( confirmation_obj.confirmation_key, user_profile.realm, Confirmation.MULTIUSE_INVITE, ), invited_as=invite.invited_as, is_multiuse=True, )) return invites
def consume(self, data: Mapping[str, Any]) -> None: invitee = filter_to_valid_prereg_users( PreregistrationUser.objects.filter(id=data["prereg_id"]) ).first() if invitee is None: # The invitation could have been revoked return referrer = get_user_profile_by_id(data["referrer_id"]) logger.info( "Sending invitation for realm %s to %s", referrer.realm.string_id, invitee.email ) activate_url = do_send_confirmation_email(invitee, referrer) # queue invitation reminder if settings.INVITATION_LINK_VALIDITY_DAYS >= 4: context = common_context(referrer) context.update( activate_url=activate_url, referrer_name=referrer.full_name, referrer_email=referrer.delivery_email, referrer_realm_name=referrer.realm.name, ) send_future_email( "zerver/emails/invitation_reminder", referrer.realm, to_emails=[invitee.email], from_address=FromAddress.tokenized_no_reply_placeholder, language=referrer.realm.default_language, context=context, delay=datetime.timedelta(days=settings.INVITATION_LINK_VALIDITY_DAYS - 2), )
def consume(self, data: Mapping[str, Any]) -> None: if "email" in data: # When upgrading from a version up through 1.7.1, there may be # existing items in the queue with `email` instead of `prereg_id`. invitee = filter_to_valid_prereg_users( PreregistrationUser.objects.filter( email__iexact=data["email"].strip())).latest("invited_at") else: invitee = filter_to_valid_prereg_users( PreregistrationUser.objects.filter( id=data["prereg_id"])).first() if invitee is None: # The invitation could have been revoked return referrer = get_user_profile_by_id(data["referrer_id"]) logger.info("Sending invitation for realm %s to %s", referrer.realm.string_id, invitee.email) activate_url = do_send_confirmation_email(invitee, referrer) # queue invitation reminder if settings.INVITATION_LINK_VALIDITY_DAYS >= 4: context = common_context(referrer) context.update({ 'activate_url': activate_url, 'referrer_name': referrer.full_name, 'referrer_email': referrer.delivery_email, 'referrer_realm_name': referrer.realm.name, }) send_future_email( "zerver/emails/invitation_reminder", referrer.realm, to_emails=[invitee.email], from_address=FromAddress.tokenized_no_reply_placeholder, language=referrer.realm.default_language, context=context, delay=datetime.timedelta( days=settings.INVITATION_LINK_VALIDITY_DAYS - 2))
def consume(self, data: Mapping[str, Any]) -> None: if "invite_expires_in_days" in data: invite_expires_in_minutes = data["invite_expires_in_days"] * 24 * 60 elif "invite_expires_in_minutes" in data: invite_expires_in_minutes = data["invite_expires_in_minutes"] invitee = filter_to_valid_prereg_users( PreregistrationUser.objects.filter(id=data["prereg_id"]), invite_expires_in_minutes).first() if invitee is None: # The invitation could have been revoked return referrer = get_user_profile_by_id(data["referrer_id"]) logger.info("Sending invitation for realm %s to %s", referrer.realm.string_id, invitee.email) if "email_language" in data: email_language = data["email_language"] else: email_language = referrer.realm.default_language activate_url = do_send_confirmation_email(invitee, referrer, email_language, invite_expires_in_minutes) if invite_expires_in_minutes is None: # We do not queue reminder email for never expiring # invitations. This is probably a low importance bug; it # would likely be more natural to send a reminder after 7 # days. return # queue invitation reminder if invite_expires_in_minutes >= 4 * 24 * 60: context = common_context(referrer) context.update( activate_url=activate_url, referrer_name=referrer.full_name, referrer_email=referrer.delivery_email, referrer_realm_name=referrer.realm.name, ) send_future_email( "zerver/emails/invitation_reminder", referrer.realm, to_emails=[invitee.email], from_address=FromAddress.tokenized_no_reply_placeholder, language=email_language, context=context, delay=datetime.timedelta(minutes=invite_expires_in_minutes - (2 * 24 * 60)), )
def get_valid_invite_confirmations_generated_by_user( user_profile: UserProfile, ) -> List[Confirmation]: prereg_user_ids = filter_to_valid_prereg_users( PreregistrationUser.objects.filter( referred_by=user_profile)).values_list("id", flat=True) confirmations = list( Confirmation.objects.filter(type=Confirmation.INVITATION, object_id__in=prereg_user_ids)) multiuse_invite_ids = MultiuseInvite.objects.filter( referred_by=user_profile).values_list("id", flat=True) confirmations += list( Confirmation.objects.filter( type=Confirmation.MULTIUSE_INVITE, object_id__in=multiuse_invite_ids, ).filter(Q(expiry_date__gte=timezone_now()) | Q(expiry_date=None))) return confirmations
def maybe_send_to_registration( request: HttpRequest, email: str, full_name: str = "", mobile_flow_otp: Optional[str] = None, desktop_flow_otp: Optional[str] = None, is_signup: bool = False, password_required: bool = True, multiuse_object_key: str = "", full_name_validated: bool = False, ) -> HttpResponse: """Given a successful authentication for an email address (i.e. we've confirmed the user controls the email address) that does not currently have a Zulip account in the target realm, send them to the registration flow or the "continue to registration" flow, depending on is_signup, whether the email address can join the organization (checked in HomepageForm), and similar details. """ # In the desktop and mobile registration flows, the sign up # happens in the browser so the user can use their # already-logged-in social accounts. Then at the end, with the # user account created, we pass the appropriate data to the app # via e.g. a `zulip://` redirect. We store the OTP keys for the # mobile/desktop flow in the session with 1-hour expiry, because # we want this configuration of having a successful authentication # result in being logged into the app to persist if the user makes # mistakes while trying to authenticate (E.g. clicks the wrong # Google account, hits back, etc.) during a given browser session, # rather than just logging into the webapp in the target browser. # # We can't use our usual pre-account-creation state storage # approach of putting something in PreregistrationUser, because # that would apply to future registration attempts on other # devices, e.g. just creating an account on the web on their laptop. assert not (mobile_flow_otp and desktop_flow_otp) if mobile_flow_otp: set_expirable_session_var( request.session, "registration_mobile_flow_otp", mobile_flow_otp, expiry_seconds=3600 ) elif desktop_flow_otp: set_expirable_session_var( request.session, "registration_desktop_flow_otp", desktop_flow_otp, expiry_seconds=3600 ) if multiuse_object_key: from_multiuse_invite = True multiuse_obj = Confirmation.objects.get(confirmation_key=multiuse_object_key).content_object realm = multiuse_obj.realm invited_as = multiuse_obj.invited_as else: from_multiuse_invite = False multiuse_obj = None try: realm = get_realm(get_subdomain(request)) except Realm.DoesNotExist: realm = None invited_as = PreregistrationUser.INVITE_AS["MEMBER"] form = HomepageForm({"email": email}, realm=realm, from_multiuse_invite=from_multiuse_invite) if form.is_valid(): # If the email address is allowed to sign up for an account in # this organization, construct a PreregistrationUser and # Confirmation objects, and then send the user to account # creation or confirm-continue-registration depending on # is_signup. try: prereg_user = filter_to_valid_prereg_users( PreregistrationUser.objects.filter(email__iexact=email, realm=realm) ).latest("invited_at") # password_required and full_name data passed here as argument should take precedence # over the defaults with which the existing PreregistrationUser that we've just fetched # was created. prereg_user.password_required = password_required update_fields = ["password_required"] if full_name: prereg_user.full_name = full_name prereg_user.full_name_validated = full_name_validated update_fields.extend(["full_name", "full_name_validated"]) prereg_user.save(update_fields=update_fields) except PreregistrationUser.DoesNotExist: prereg_user = create_preregistration_user( email, request, password_required=password_required, full_name=full_name, full_name_validated=full_name_validated, ) if multiuse_obj is not None: request.session.modified = True streams_to_subscribe = list(multiuse_obj.streams.all()) prereg_user.streams.set(streams_to_subscribe) prereg_user.invited_as = invited_as prereg_user.save() confirmation_link = create_confirmation_link(prereg_user, Confirmation.USER_REGISTRATION) if is_signup: return redirect(confirmation_link) context = {"email": email, "continue_link": confirmation_link, "full_name": full_name} return render(request, "zerver/confirm_continue_registration.html", context=context) # This email address it not allowed to join this organization, so # just send the user back to the registration page. url = reverse("register") context = login_context(request) extra_context: Mapping[str, Any] = { "form": form, "current_url": lambda: url, "from_multiuse_invite": from_multiuse_invite, "multiuse_object_key": multiuse_object_key, "mobile_flow_otp": mobile_flow_otp, "desktop_flow_otp": desktop_flow_otp, } context.update(extra_context) return render(request, "zerver/accounts_home.html", context=context)