def retrieve_v2_profile(request, username=None, from_db=False):
    """Helper method to retrieve a profile either from the v2 schema or
    from the database.
    """

    if not username and not request.user.is_authenticated():
        return None
    username_q = username or request.user.username

    if from_db:
        # This is a db query, let's return the user
        profile = get_object_or_none(User, username=username_q)
        return profile

    # We need to fetch data from ES
    orgchart_related_data = None
    if not username:
        # This is a query to get a minified version of a profile
        # for the user menu
        profile_data = search_get_profile(request, username_q,
                                          UserAccessLevel.PRIVATE)
    else:
        profile_data = search_get_profile(request, username_q)
        orgchart_related_data = orgchart_get_by_username(
            request, 'related', username_q)

    profile = json2obj(profile_data.content)
    if orgchart_related_data:
        profile.update(json2obj(orgchart_related_data.content))

    return profile
Example #2
0
    def handle(self, *args, **options):
        path = options.get('file', None)
        dry_run = options.get('dry-run')

        if not path:
            raise CommandError('Option --file must be specified')

        try:
            f = open(path)
        except IOError:
            raise CommandError('Invalid file path.')

        now = timezone.now()
        former_employee_descr = 'An automatic vouch for being a former Mozilla employee.'
        employee_descr = 'An automatic vouch for being a Mozilla employee.'

        count = 0
        for email in f:
            u = get_object_or_none(UserProfile, user__email=email.strip())
            if u:
                vouches = u.vouches_received.all()
                already_vouched = vouches.filter(
                    Q(description=employee_descr)
                    | Q(description=former_employee_descr),
                    autovouch=True)
                if not already_vouched.exists():
                    if not dry_run:
                        Vouch.objects.create(voucher=None,
                                             vouchee=u,
                                             autovouch=True,
                                             date=now,
                                             description=former_employee_descr)
                    count = count + 1

        print "%d former staff members vouched." % count
Example #3
0
def subscribe_user_task(self, result, email='', newsletters=[], sync='N', optin='Y'):
    """Subscribes a user to basket newsletters.

    The email to subscribe is provided either from the result of the lookup task
    or from the profile of the user.
    """

    if not result and not email:
        return None

    from mozillians.users.models import UserProfile
    profile = get_object_or_none(UserProfile, user__email=email)

    newsletters_to_subscribe = []
    if result.get('status') == 'ok':
        # This is used when we want to subscribe a different email
        # than the one in the lookup (eg when a user changes emails)
        if not email:
            email = result.get('email')

        if newsletters:
            newsletters_to_subscribe = list(set(newsletters) - set(result['newsletters']))
        else:
            # This case is used when a user changes email.
            # The lookup task will provide the newsletters that the user was registered.
            # We need to find the common with the mozillians newsletters and
            # subscribe the email provided as an argument.
            newsletters_to_subscribe = list(set(MOZILLIANS_NEWSLETTERS)
                                            .intersection(result['newsletters']))

    # The lookup failed because the user does not exist. We have a new user!
    if (result.get('status') == 'error'
            and result.get('desc') == u'User not found' and newsletters):
        newsletters_to_subscribe = newsletters

    if newsletters_to_subscribe:
        try:
            kwargs = {
                'sync': sync,
                'source_url': MOZILLIANS_URL,
                'optin': optin,
                'api_key': BASKET_API_KEY
            }

            if profile and profile.country:
                kwargs['country'] = profile.country.code2

            subscribe_result = basket.subscribe(email,
                                                newsletters_to_subscribe,
                                                **kwargs)
        except MaxRetriesExceededError as exc:
            raise exc
        except basket.BasketException as exc:
            raise self.retry(exc=exc)
        return subscribe_result
    return None
Example #4
0
def subscribe_user_task(self, result, email='', newsletters=[], sync='N', optin='Y'):
    """Subscribes a user to basket newsletters.

    The email to subscribe is provided either from the result of the lookup task
    or from the profile of the user.
    """

    if not result and not email:
        return None

    from mozillians.users.models import UserProfile
    profile = get_object_or_none(UserProfile, user__email=email)

    newsletters_to_subscribe = []
    if result.get('status') == 'ok':
        # This is used when we want to subscribe a different email
        # than the one in the lookup (eg when a user changes emails)
        if not email:
            email = result.get('email')

        if newsletters:
            newsletters_to_subscribe = list(set(newsletters) - set(result['newsletters']))
        else:
            # This case is used when a user changes email.
            # The lookup task will provide the newsletters that the user was registered.
            # We need to find the common with the mozillians newsletters and
            # subscribe the email provided as an argument.
            newsletters_to_subscribe = list(set(MOZILLIANS_NEWSLETTERS)
                                            .intersection(result['newsletters']))

    # The lookup failed because the user does not exist. We have a new user!
    if (result.get('status') == 'error' and
            result.get('desc') == u'User not found' and newsletters):
        newsletters_to_subscribe = newsletters

    if newsletters_to_subscribe:
        try:
            kwargs = {
                'sync': sync,
                'source_url': MOZILLIANS_URL,
                'optin': optin,
                'api_key': BASKET_API_KEY
            }

            if profile and profile.country:
                kwargs['country'] = profile.country.code2

            subscribe_result = basket.subscribe(email,
                                                newsletters_to_subscribe,
                                                **kwargs)
        except MaxRetriesExceededError as exc:
            raise exc
        except basket.BasketException as exc:
            raise self.retry(exc=exc)
        return subscribe_result
    return None
Example #5
0
    def add_member(self, userprofile, status=GroupMembership.MEMBER, inviter=None):
        """
        Add a user to this group. Optionally specify status other than member.

        If user is already in the group with the given status, this is a no-op.

        If user is already in the group with a different status, their status will
        be updated if the change is a promotion. Otherwise, their status will not change.

        If the group in question is the NDA group, also add the user to the NDA newsletter.
        """
        defaults = dict(status=status, date_joined=now())
        membership, _ = GroupMembership.objects.get_or_create(userprofile=userprofile,
                                                              group=self,
                                                              defaults=defaults)

        send_userprofile_to_cis.delay(membership.userprofile.pk)

        # Remove the need_removal flag in any case
        # We have a renewal, let's save the object.
        if membership.needs_renewal:
            membership.needs_renewal = False
            membership.save()

        if membership.status != status:
            # Status changed
            # The only valid status change states are:
            # PENDING to MEMBER
            # PENDING to PENDING_TERMS
            # PENDING_TERMS to MEMBER

            old_status = membership.status
            membership.status = status
            statuses = [(GroupMembership.PENDING, GroupMembership.MEMBER),
                        (GroupMembership.PENDING, GroupMembership.PENDING_TERMS),
                        (GroupMembership.PENDING_TERMS, GroupMembership.MEMBER)]

            if (old_status, status) in statuses:
                # Status changed
                membership.save()
                if membership.status in [GroupMembership.PENDING, GroupMembership.MEMBER]:
                    email_membership_change.delay(self.pk, userprofile.user.pk, old_status, status)

                # Since there is no demotion, we can check if the new status is MEMBER and
                # subscribe the user to the NDA newsletter if the group is NDA
                if self.name == settings.NDA_GROUP and status == GroupMembership.MEMBER:
                    subscribe_user_to_basket.delay(userprofile.id,
                                                   [settings.BASKET_NDA_NEWSLETTER])

        if inviter:
            # Set the invite to the last person who renewed the membership
            invite = get_object_or_none(Invite, group=membership.group, redeemer=userprofile)
            if invite:
                invite.inviter = inviter
                invite.save()
Example #6
0
    def add_member(self, userprofile, status=GroupMembership.MEMBER, inviter=None):
        """
        Add a user to this group. Optionally specify status other than member.

        If user is already in the group with the given status, this is a no-op.

        If user is already in the group with a different status, their status will
        be updated if the change is a promotion. Otherwise, their status will not change.

        If the group in question is the NDA group, also add the user to the NDA newsletter.
        """
        defaults = dict(status=status, date_joined=now())
        membership, _ = GroupMembership.objects.get_or_create(userprofile=userprofile,
                                                              group=self,
                                                              defaults=defaults)

        send_userprofile_to_cis.delay(membership.userprofile.pk)

        # Remove the need_removal flag in any case
        # We have a renewal, let's save the object.
        if membership.needs_renewal:
            membership.needs_renewal = False
            membership.save()

        if membership.status != status:
            # Status changed
            # The only valid status change states are:
            # PENDING to MEMBER
            # PENDING to PENDING_TERMS
            # PENDING_TERMS to MEMBER

            old_status = membership.status
            membership.status = status
            statuses = [(GroupMembership.PENDING, GroupMembership.MEMBER),
                        (GroupMembership.PENDING, GroupMembership.PENDING_TERMS),
                        (GroupMembership.PENDING_TERMS, GroupMembership.MEMBER)]

            if (old_status, status) in statuses:
                # Status changed
                membership.save()
                if membership.status in [GroupMembership.PENDING, GroupMembership.MEMBER]:
                    email_membership_change.delay(self.pk, userprofile.user.pk, old_status, status)

                # Since there is no demotion, we can check if the new status is MEMBER and
                # subscribe the user to the NDA newsletter if the group is NDA
                if self.name == settings.NDA_GROUP and status == GroupMembership.MEMBER:
                    subscribe_user_to_basket.delay(userprofile.id,
                                                   [settings.BASKET_NDA_NEWSLETTER])

        if inviter:
            # Set the invite to the last person who renewed the membership
            invite = get_object_or_none(Invite, group=membership.group, redeemer=userprofile)
            if invite:
                invite.inviter = inviter
                invite.save()
Example #7
0
    def check_authentication_method(self, user):
        """Check which Identity is used to login.

        This method, depending on the current status of the IdpProfile
        of a user, enforces MFA logins and creates the IdpProfiles.
        Returns the object (user) it was passed unchanged.
        """
        if not user:
            return None

        profile = user.userprofile
        # Ensure compatibility with OIDC conformant mode
        auth0_user_id = self.claims.get('user_id') or self.claims.get('sub')
        email = self.claims.get('email')

        # Get current_idp
        current_idp = get_object_or_none(IdpProfile,
                                         profile=profile,
                                         primary=True)

        # Get or create new `user_id`
        obj, _ = IdpProfile.objects.get_or_create(profile=profile,
                                                  email=email,
                                                  auth0_user_id=auth0_user_id)

        # Update/Save the Github username
        if 'github|' in auth0_user_id:
            obj.username = self.claims.get('nickname', '')
            obj.save()

        # Do not allow downgrades.
        # Round the current type to the floor.
        # This way 30 and 39 will have always the same priority.
        if current_idp and obj.type < int(
                math.floor(current_idp.type / 10) * 10):
            msg = u'Please use one of the following authentication methods: {}'
            # convert the tuple to a dict to easily get the values
            provider_types = dict(IdpProfile.PROVIDER_TYPES)
            methods = ', '.join(provider_types[x]
                                for x in ALLOWED_IDP_FLOWS[current_idp.type])
            messages.error(self.request, msg.format(methods))
            return None

        # Mark other `user_id` as `primary=False`
        idp_q = IdpProfile.objects.filter(profile=profile)
        with transaction.atomic():
            idp_q.exclude(auth0_user_id=auth0_user_id,
                          email=email).update(primary=False)
            # Mark current `user_id` as `primary=True`
            idp_q.filter(auth0_user_id=auth0_user_id,
                         email=email).update(primary=True)
            # Update CIS
            send_userprofile_to_cis.delay(profile.pk)
        return user
Example #8
0
    def check_authentication_method(self, user):
        """Check which Identity is used to login.

        This method, depending on the current status of the IdpProfile
        of a user, enforces MFA logins and creates the IdpProfiles.
        Returns the object (user) it was passed unchanged.
        """
        if not user:
            return None

        profile = user.userprofile
        # Ensure compatibility with OIDC conformant mode
        auth0_user_id = self.claims.get('user_id') or self.claims.get('sub')
        email = self.claims.get('email')
        # Grant an employee vouch if the user has the 'hris_is_staff' group
        groups = self.claims.get('https://sso.mozilla.com/claim/groups')
        if groups and 'hris_is_staff' in groups:
            profile.auto_vouch()

        # Get current_idp
        current_idp = get_object_or_none(IdpProfile,
                                         profile=profile,
                                         primary=True)

        # Get or create new `user_id`
        obj, _ = IdpProfile.objects.get_or_create(profile=profile,
                                                  email=email,
                                                  auth0_user_id=auth0_user_id)

        # Update/Save the Github username
        if 'github|' in auth0_user_id:
            obj.username = self.claims.get('nickname', '')
            obj.save()

        # Do not allow downgrades.
        if current_idp and obj.type < current_idp.type:
            msg = u'Please use {0} as the login method to authenticate'
            messages.error(self.request,
                           msg.format(current_idp.get_type_display()))
            return None

        # Mark other `user_id` as `primary=False`
        idp_q = IdpProfile.objects.filter(profile=profile)
        with transaction.atomic():
            idp_q.exclude(auth0_user_id=auth0_user_id,
                          email=email).update(primary=False)
            # Mark current `user_id` as `primary=True`
            idp_q.filter(auth0_user_id=auth0_user_id,
                         email=email).update(primary=True)
            # Update CIS
            send_userprofile_to_cis.delay(profile.pk)
        return user
Example #9
0
    def add_member(self,
                   userprofile,
                   status=GroupMembership.MEMBER,
                   inviter=None):
        """
        Add a user to this group. Optionally specify status other than member.

        If user is already in the group with the given status, this is a no-op.

        If user is already in the group with a different status, their status will
        be updated if the change is a promotion. Otherwise, their status will not change.

        If the group in question is the NDA group, also add the user to the NDA newsletter.
        """
        defaults = dict(status=status, date_joined=now())
        membership, _ = GroupMembership.objects.get_or_create(
            userprofile=userprofile, group=self, defaults=defaults)

        # Remove the need_removal flag in any case
        # We have a renewal, let's save the object.
        if membership.needs_renewal:
            membership.needs_renewal = False
            membership.save()

        if membership.status != status:
            # Status changed
            # The only valid status change states are:
            # PENDING to MEMBER
            # PENDING to PENDING_TERMS
            # PENDING_TERMS to MEMBER

            old_status = membership.status
            membership.status = status
            statuses = [
                (GroupMembership.PENDING, GroupMembership.MEMBER),
                (GroupMembership.PENDING, GroupMembership.PENDING_TERMS),
                (GroupMembership.PENDING_TERMS, GroupMembership.MEMBER)
            ]

            if (old_status, status) in statuses:
                # Status changed
                membership.save()

        if inviter:
            # Set the invite to the last person who renewed the membership
            invite = get_object_or_none(Invite,
                                        group=membership.group,
                                        redeemer=userprofile)
            if invite:
                invite.inviter = inviter
                invite.save()
Example #10
0
def bundle_profile_data(profile_id, delete=False):
    """Packs all the Identity Profiles of a user into a dictionary."""
    from mozillians.common.templatetags.helpers import get_object_or_none
    from mozillians.users.models import IdpProfile, UserProfile

    try:
        profile = UserProfile.objects.get(pk=profile_id)
    except UserProfile.DoesNotExist:
        return []
    human_name = HumanName(profile.full_name)

    primary_idp = get_object_or_none(IdpProfile, profile=profile, primary=True)
    primary_login_email = profile.email
    if primary_idp:
        primary_login_email = primary_idp.email

    results = []
    for idp in profile.idp_profiles.all():

        data = {
            'user_id': idp.auth0_user_id,
            'timezone': profile.timezone,
            'active': profile.user.is_active,
            'lastModified': profile.last_updated.isoformat(),
            'created': profile.user.date_joined.isoformat(),
            'userName': profile.user.username,
            'displayName': profile.display_name,
            'primaryEmail': primary_login_email,
            'emails': profile.get_cis_emails(),
            'uris': profile.get_cis_uris(),
            'picture': profile.get_photo_url(),
            'shirtSize': profile.get_tshirt_display() or '',
            'groups': [] if delete else profile.get_cis_groups(idp),
            'tags': [] if delete else profile.get_cis_tags(),

            # Derived fields
            'firstName': human_name.first,
            'lastName': human_name.last,

            # Hardcoded fields
            'preferredLanguage': 'en_US',
            'phoneNumbers': [],
            'nicknames': [],
            'SSHFingerprints': [],
            'PGPFingerprints': [],
            'authoritativeGroups': []
        }
        results.append(data)
    return results
Example #11
0
def bundle_profile_data(profile_id, delete=False):
    """Packs all the Identity Profiles of a user into a dictionary."""
    from mozillians.common.templatetags.helpers import get_object_or_none
    from mozillians.users.models import IdpProfile, UserProfile

    try:
        profile = UserProfile.objects.get(pk=profile_id)
    except UserProfile.DoesNotExist:
        return []
    human_name = HumanName(profile.full_name)

    primary_idp = get_object_or_none(IdpProfile, profile=profile, primary=True)
    primary_login_email = profile.email
    if primary_idp:
        primary_login_email = primary_idp.email

    results = []
    for idp in profile.idp_profiles.all():

        data = {
            'user_id': idp.auth0_user_id,
            'timezone': profile.timezone,
            'active': profile.user.is_active,
            'lastModified': profile.last_updated.isoformat(),
            'created': profile.user.date_joined.isoformat(),
            'userName': profile.user.username,
            'displayName': profile.display_name,
            'primaryEmail': primary_login_email,
            'emails': profile.get_cis_emails(),
            'uris': profile.get_cis_uris(),
            'picture': profile.get_photo_url(),
            'shirtSize': profile.get_tshirt_display() or '',
            'groups': [] if delete else profile.get_cis_groups(idp),
            'tags': [] if delete else profile.get_cis_tags(),

            # Derived fields
            'firstName': human_name.first,
            'lastName': human_name.last,

            # Hardcoded fields
            'preferredLanguage': 'en_US',
            'phoneNumbers': [],
            'nicknames': [],
            'SSHFingerprints': [],
            'PGPFingerprints': [],
            'authoritativeGroups': []
        }
        results.append(data)
    return results
def check_spam_account(instance_id, **kwargs):
    """Task to check if profile is spam according to akismet"""
    # Avoid circular dependencies
    from mozillians.users.models import AbuseReport, UserProfile

    spam = akismet_spam_check(**kwargs)
    profile = get_object_or_none(UserProfile, id=instance_id)

    if spam and profile:
        kwargs = {
            'type': AbuseReport.TYPE_SPAM,
            'profile': profile,
            'reporter': None,
            'is_akismet': True,
        }

        AbuseReport.objects.get_or_create(**kwargs)
Example #13
0
def check_spam_account(instance_id, **kwargs):
    """Task to check if profile is spam according to akismet"""
    # Avoid circular dependencies
    from mozillians.users.models import AbuseReport, UserProfile

    spam = akismet_spam_check(**kwargs)
    profile = get_object_or_none(UserProfile, id=instance_id)

    if spam and profile:
        kwargs = {
            'type': AbuseReport.TYPE_SPAM,
            'profile': profile,
            'reporter': None,
            'is_akismet': True,
        }

        AbuseReport.objects.get_or_create(**kwargs)
Example #14
0
    def check_authentication_method(self, user):
        """Check which Identity is used to login.

        This method, depending on the current status of the IdpProfile
        of a user, enforces MFA logins and creates the IdpProfiles.
        Returns the object (user) it was passed unchanged.
        """
        if not user:
            return None

        profile = user.userprofile
        auth0_user_id = self.claims.get('user_id')
        email = self.claims.get('email')

        # Get current_idp
        current_idp = get_object_or_none(IdpProfile,
                                         profile=profile,
                                         primary=True)

        # Get or create new `user_id`
        obj, _ = IdpProfile.objects.get_or_create(profile=profile,
                                                  email=email,
                                                  auth0_user_id=auth0_user_id)

        # Do not allow downgrades.
        if current_idp and obj.type < current_idp.type:
            msg = u'Please use one of the following authentication methods: {}'
            # convert the tuple to a dict to easily get the values
            provider_types = dict(IdpProfile.PROVIDER_TYPES)
            methods = ', '.join(provider_types[x]
                                for x in ALLOWED_IDP_FLOWS[current_idp.type])
            messages.error(self.request, msg.format(methods))
            return None

        # Mark other `user_id` as `primary=False`
        idp_q = IdpProfile.objects.filter(profile=profile)
        with transaction.atomic():
            idp_q.exclude(auth0_user_id=auth0_user_id,
                          email=email).update(primary=False)
            # Mark current `user_id` as `primary=True`
            idp_q.filter(auth0_user_id=auth0_user_id,
                         email=email).update(primary=True)
            # Update CIS
            send_userprofile_to_cis.delay(profile.pk)
        return user
Example #15
0
    def check_authentication_method(self, user):
        """Check which Identity is used to login.

        This method, depending on the current status of the IdpProfile
        of a user, enforces MFA logins and creates the IdpProfiles.
        Returns the object (user) it was passed unchanged.
        """
        if not user:
            return None

        profile = user.userprofile
        # Ensure compatibility with OIDC conformant mode
        auth0_user_id = self.claims.get('user_id') or self.claims.get('sub')
        email = self.claims.get('email')

        # Get current_idp
        current_idp = get_object_or_none(IdpProfile, profile=profile, primary=True)

        # Get or create new `user_id`
        obj, _ = IdpProfile.objects.get_or_create(
            profile=profile,
            email=email,
            auth0_user_id=auth0_user_id)

        # Update/Save the Github username
        if 'github|' in auth0_user_id:
            obj.username = self.claims.get('nickname', '')
            obj.save()

        # Do not allow downgrades.
        if current_idp and obj.type < current_idp.type:
            msg = u'Please use {0} as the login method to authenticate'
            messages.error(self.request, msg.format(current_idp.get_type_display()))
            return None

        # Mark other `user_id` as `primary=False`
        idp_q = IdpProfile.objects.filter(profile=profile)
        with transaction.atomic():
            idp_q.exclude(auth0_user_id=auth0_user_id, email=email).update(primary=False)
            # Mark current `user_id` as `primary=True`
            idp_q.filter(auth0_user_id=auth0_user_id, email=email).update(primary=True)
            # Update CIS
            send_userprofile_to_cis.delay(profile.pk)
        return user
Example #16
0
    def check_authentication_method(self, user):
        """Check which Identity is used to login.

        This method, depending on the current status of the IdpProfile
        of a user, enforces MFA logins and creates the IdpProfiles.
        Returns the object (user) it was passed unchanged.
        """
        if not user:
            return None

        profile = user.userprofile
        # Ensure compatibility with OIDC conformant mode
        auth0_user_id = self.claims.get('user_id') or self.claims.get('sub')
        email = self.claims.get('email')

        # Get current_idp
        current_idp = get_object_or_none(IdpProfile, profile=profile, primary=True)

        # Get or create new `user_id`
        obj, _ = IdpProfile.objects.get_or_create(
            profile=profile,
            email=email,
            auth0_user_id=auth0_user_id)

        # Do not allow downgrades.
        # Round the current type to the floor. This way 30 and 39 will have always the same
        # priority.
        if current_idp and obj.type < int(math.floor(current_idp.type / 10) * 10):
            msg = u'Please use one of the following authentication methods: {}'
            # convert the tuple to a dict to easily get the values
            provider_types = dict(IdpProfile.PROVIDER_TYPES)
            methods = ', '.join(provider_types[x] for x in ALLOWED_IDP_FLOWS[current_idp.type])
            messages.error(self.request, msg.format(methods))
            return None

        # Mark other `user_id` as `primary=False`
        idp_q = IdpProfile.objects.filter(profile=profile)
        with transaction.atomic():
            idp_q.exclude(auth0_user_id=auth0_user_id, email=email).update(primary=False)
            # Mark current `user_id` as `primary=True`
            idp_q.filter(auth0_user_id=auth0_user_id, email=email).update(primary=True)
            # Update CIS
            send_userprofile_to_cis.delay(profile.pk)
        return user
Example #17
0
    def get_results(self, context):
        """Modify the text in the results of the group invitation form."""

        results = []
        for result in context['object_list']:
            pk = self.get_result_value(result)
            if not pk:
                continue

            profile = UserProfile.objects.get(pk=pk)
            idp = get_object_or_none(IdpProfile, profile=profile, primary=True)
            text = self.get_result_label(result)

            # Append the email used for login in the autocomplete text
            if idp:
                text += ' ({0})'.format(idp.email)

            item = {'id': pk, 'text': text}
            results.append(item)
        return results
    def handle(self, *args, **options):
        path = options.get('file', None)
        dry_run = options.get('dry-run')

        if not path:
            raise CommandError('Option --file must be specified')

        try:
            f = open(path)
        except IOError:
            raise CommandError('Invalid file path.')

        now = timezone.now()
        former_employee_descr = 'An automatic vouch for being a former Mozilla employee.'
        employee_descr = 'An automatic vouch for being a Mozilla employee.'

        count = 0
        for email in f:
            u = get_object_or_none(UserProfile, user__email=email.strip())
            if u:
                vouches = u.vouches_received.all()
                already_vouched = vouches.filter(
                    Q(description=employee_descr) |
                    Q(description=former_employee_descr),
                    autovouch=True
                )
                if not already_vouched.exists():
                    if not dry_run:
                        Vouch.objects.create(
                            voucher=None,
                            vouchee=u,
                            autovouch=True,
                            date=now,
                            description=former_employee_descr
                        )
                    count = count + 1

        print "%d former staff members vouched." % count
Example #19
0
def retrieve_v2_profile(request, username, from_db=False):
    """Helper method to retrieve a profile either from the v2 schema or
    from the database.
    """
    profile_username = None
    if request.user.is_authenticated():
        profile_username = request.user.username
    username_q = username or profile_username
    if not username_q:
        return None

    if from_db:
        profile = get_object_or_none(User, username=username_q)
    else:
        # We need to fetch data from ES
        profile_data = search_get_profile(request, username_q)
        orgchart_related_data = orgchart_get_by_username(
            request, 'related', username_q)

        profile = json2obj(profile_data.content)
        profile.update(json2obj(orgchart_related_data.content))

    return profile
Example #20
0
    def get_results(self, context):
        """Modify the text in the results of the group invitation form."""

        results = []
        for result in context['object_list']:
            pk = self.get_result_value(result)
            if not pk:
                continue

            profile = UserProfile.objects.get(pk=pk)
            idp = get_object_or_none(IdpProfile, profile=profile, primary=True)
            text = self.get_result_label(result)

            # Append the email used for login in the autocomplete text
            if idp:
                text += ' ({0})'.format(idp.email)

            item = {
                'id': pk,
                'text': text
            }
            results.append(item)
        return results
Example #21
0
def edit_profile(request):
    """Edit user profile view."""
    # Don't use request.user
    user = User.objects.get(pk=request.user.id)
    profile = user.userprofile
    user_groups = profile.groups.all().order_by('name')
    idp_profiles = IdpProfile.objects.filter(profile=profile)
    idp_primary_profile = get_object_or_none(IdpProfile, profile=profile, primary=True)
    # The accounts that a user can select as the primary login identity
    accounts_qs = ExternalAccount.objects.exclude(type=ExternalAccount.TYPE_EMAIL)

    sections = {
        'registration_section': ['user_form', 'registration_form'],
        'basic_section': ['user_form', 'basic_information_form'],
        'groups_section': ['groups_privacy_form'],
        'skills_section': ['skills_form'],
        'idp_section': ['idp_profile_formset'],
        'languages_section': ['language_privacy_form', 'language_formset'],
        'accounts_section': ['accounts_formset'],
        'location_section': ['location_form'],
        'irc_section': ['irc_form'],
        'contribution_section': ['contribution_form'],
        'tshirt_section': ['tshirt_form'],
    }

    curr_sect = next((s for s in sections.keys() if s in request.POST), None)

    def get_request_data(form):
        if curr_sect and form in sections[curr_sect]:
            return request.POST
        return None

    ctx = {}
    ctx['user_form'] = forms.UserForm(get_request_data('user_form'), instance=user)
    ctx['registration_form'] = forms.RegisterForm(get_request_data('registration_form'),
                                                  request.FILES or None,
                                                  instance=profile)
    basic_information_data = get_request_data('basic_information_form')
    ctx['basic_information_form'] = forms.BasicInformationForm(basic_information_data,
                                                               request.FILES or None,
                                                               instance=profile)
    ctx['accounts_formset'] = forms.AccountsFormset(get_request_data('accounts_formset'),
                                                    instance=profile,
                                                    queryset=accounts_qs)
    ctx['location_form'] = forms.LocationForm(get_request_data('location_form'), instance=profile)
    ctx['language_formset'] = forms.LanguagesFormset(get_request_data('language_formset'),
                                                     instance=profile,
                                                     locale=request.locale)
    language_privacy_data = get_request_data('language_privacy_form')
    ctx['language_privacy_form'] = forms.LanguagesPrivacyForm(language_privacy_data,
                                                              instance=profile)
    ctx['skills_form'] = forms.SkillsForm(get_request_data('skills_form'), instance=profile)
    ctx['contribution_form'] = forms.ContributionForm(get_request_data('contribution_form'),
                                                      instance=profile)
    ctx['tshirt_form'] = forms.TshirtForm(get_request_data('tshirt_form'), instance=profile)
    ctx['groups_privacy_form'] = forms.GroupsPrivacyForm(get_request_data('groups_privacy_form'),
                                                         instance=profile)
    ctx['irc_form'] = forms.IRCForm(get_request_data('irc_form'), instance=profile)
    ctx['idp_profile_formset'] = forms.IdpProfileFormset(get_request_data('idp_profile_formset'),
                                                         instance=profile,
                                                         queryset=idp_profiles)
    ctx['idp_primary_profile'] = idp_primary_profile

    ctx['autocomplete_form_media'] = ctx['registration_form'].media + ctx['skills_form'].media
    forms_valid = True
    if request.POST:
        if not curr_sect:
            raise Http404
        curr_forms = map(lambda x: ctx[x], sections[curr_sect])
        forms_valid = all(map(lambda x: x.is_valid(), curr_forms))
        if forms_valid:
            old_username = request.user.username
            for f in curr_forms:
                f.save()

            # Spawn task to check for spam
            if not profile.is_vouched:
                x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
                if x_forwarded_for:
                    user_ip = x_forwarded_for.split(',')[0]
                else:
                    user_ip = request.META.get('REMOTE_ADDR')

                params = {
                    'instance_id': profile.id,
                    'user_ip': user_ip,
                    'user_agent': request.META.get('HTTP_USER_AGENT'),
                    'referrer': request.META.get('HTTP_REFERER'),
                    'comment_author': profile.full_name,
                    'comment_author_email': profile.email,
                    'comment_content': profile.bio
                }

                check_spam_account.delay(**params)

            next_section = request.GET.get('next')
            next_url = urlparams(reverse('phonebook:profile_edit'), next_section)
            if curr_sect == 'registration_section':
                settings_url = reverse('phonebook:profile_edit')
                settings_link = '<a href="{0}">settings</a>'.format(settings_url)
                msg = _(u'Your registration is complete. '
                        u'Feel free to visit the {0} page to add '
                        u'additional information to your profile.'.format(settings_link))
                messages.info(request, mark_safe(msg))
                redeem_invite(profile, request.session.get('invite-code'))
                next_url = reverse('phonebook:profile_view', args=[user.username])
            elif user.username != old_username:
                msg = _(u'You changed your username; '
                        u'please note your profile URL has also changed.')
                messages.info(request, _(msg))
            return HttpResponseRedirect(next_url)

    ctx.update({
        'user_groups': user_groups,
        'profile': request.user.userprofile,
        'vouch_threshold': settings.CAN_VOUCH_THRESHOLD,
        'appsv2': profile.apps.filter(enabled=True),
        'forms_valid': forms_valid
    })

    return render(request, 'phonebook/edit_profile.html', ctx)
Example #22
0
def view_profile(request, username):
    """View a profile by username."""
    data = {}
    privacy_mappings = {'anonymous': PUBLIC, 'mozillian': MOZILLIANS, 'employee': EMPLOYEES,
                        'privileged': PRIVILEGED, 'myself': None}
    privacy_level = None
    abuse_form = None

    if (request.user.is_authenticated() and request.user.username == username):
        # own profile
        view_as = request.GET.get('view_as', 'myself')
        privacy_level = privacy_mappings.get(view_as, None)
        profile = UserProfile.objects.privacy_level(privacy_level).get(user__username=username)
        data['privacy_mode'] = view_as
    else:
        userprofile_query = UserProfile.objects.filter(user__username=username)
        public_profile_exists = userprofile_query.public().exists()
        profile_exists = userprofile_query.exists()
        profile_complete = userprofile_query.exclude(full_name='').exists()

        if not public_profile_exists:
            if not request.user.is_authenticated():
                # you have to be authenticated to continue
                messages.warning(request, LOGIN_MESSAGE)
                return (login_required(view_profile, login_url=reverse('phonebook:home'))
                        (request, username))

            if not request.user.userprofile.is_vouched:
                # you have to be vouched to continue
                messages.error(request, GET_VOUCHED_MESSAGE)
                return redirect('phonebook:home')

        if not profile_exists or not profile_complete:
            raise Http404

        profile = UserProfile.objects.get(user__username=username)
        profile.set_instance_privacy_level(PUBLIC)
        if request.user.is_authenticated():
            profile.set_instance_privacy_level(
                request.user.userprofile.privacy_level)

        if (request.user.is_authenticated() and request.user.userprofile.is_vouched and
                not profile.can_vouch):
            abuse_report = get_object_or_none(AbuseReport, reporter=request.user.userprofile,
                                              profile=profile)

            if not abuse_report:
                abuse_report = AbuseReport(reporter=request.user.userprofile, profile=profile)

            abuse_form = forms.AbuseReportForm(request.POST or None, instance=abuse_report)
            if abuse_form.is_valid():
                abuse_form.save()
                msg = _(u'Thanks for helping us improve mozillians.org!')
                messages.info(request, msg)
                return redirect('phonebook:profile_view', profile.user.username)

        if (request.user.is_authenticated() and profile.is_vouchable(request.user.userprofile)):

            vouch_form = forms.VouchForm(request.POST or None)
            data['vouch_form'] = vouch_form
            if vouch_form.is_valid():
                # We need to re-fetch profile from database.
                profile = UserProfile.objects.get(user__username=username)
                profile.vouch(request.user.userprofile, vouch_form.cleaned_data['description'])
                # Notify the current user that they vouched successfully.
                msg = _(u'Thanks for vouching for a fellow Mozillian! This user is now vouched!')
                messages.info(request, msg)
                return redirect('phonebook:profile_view', profile.user.username)

    data['shown_user'] = profile.user
    data['profile'] = profile
    data['groups'] = profile.get_annotated_groups()
    data['abuse_form'] = abuse_form

    # Only show pending groups if user is looking at their own profile,
    # or current user is a superuser
    if not (request.user.is_authenticated() and
            (request.user.username == username or request.user.is_superuser)):
        data['groups'] = [grp for grp in data['groups'] if not (grp.pending or grp.pending_terms)]

    return render(request, 'phonebook/profile.html', data)
Example #23
0
def edit_profile(request):
    """Edit user profile view."""
    # Don't use request.user
    user = User.objects.get(pk=request.user.id)
    profile = user.userprofile
    idp_profiles = IdpProfile.objects.filter(profile=profile)
    idp_primary_profile = get_object_or_none(IdpProfile,
                                             profile=profile,
                                             primary=True)
    # The accounts that a user can select as the primary login identity
    accounts_qs = ExternalAccount.objects.exclude(
        type=ExternalAccount.TYPE_EMAIL)

    sections = {
        'basic_section': ['user_form', 'basic_information_form'],
        'idp_section': ['idp_profile_formset'],
        'accounts_section': ['accounts_formset'],
        'location_section': ['location_form'],
        'contribution_section': ['contribution_form'],
    }

    curr_sect = next((s for s in sections.keys() if s in request.POST), None)

    def get_request_data(form):
        if curr_sect and form in sections[curr_sect]:
            return request.POST
        return None

    ctx = {}
    ctx['user_form'] = forms.UserForm(get_request_data('user_form'),
                                      instance=user)
    basic_information_data = get_request_data('basic_information_form')
    ctx['basic_information_form'] = forms.BasicInformationForm(
        basic_information_data, request.FILES or None, instance=profile)
    ctx['accounts_formset'] = forms.AccountsFormset(
        get_request_data('accounts_formset'),
        instance=profile,
        queryset=accounts_qs)
    ctx['location_form'] = forms.LocationForm(
        get_request_data('location_form'), instance=profile)
    ctx['contribution_form'] = forms.ContributionForm(
        get_request_data('contribution_form'), instance=profile)
    ctx['idp_profile_formset'] = forms.IdpProfileFormset(
        get_request_data('idp_profile_formset'),
        instance=profile,
        queryset=idp_profiles)
    ctx['idp_primary_profile'] = idp_primary_profile

    forms_valid = True
    if request.POST:
        if not curr_sect:
            raise Http404
        curr_forms = map(lambda x: ctx[x], sections[curr_sect])
        forms_valid = all(map(lambda x: x.is_valid(), curr_forms))
        if forms_valid:
            old_username = request.user.username
            for f in curr_forms:
                f.save()

            next_section = request.GET.get('next')
            next_url = urlparams(reverse('phonebook:profile_edit'),
                                 next_section)
            if user.username != old_username:
                msg = _(u'You changed your username; '
                        u'please note your profile URL has also changed.')
                messages.info(request, _(msg))
            return HttpResponseRedirect(next_url)

    ctx.update({
        'profile': request.user.userprofile,
        'vouch_threshold': settings.CAN_VOUCH_THRESHOLD,
        'appsv2': profile.apps.filter(enabled=True),
        'forms_valid': forms_valid
    })

    return render(request, 'phonebook/edit_profile.html', ctx)
Example #24
0
def show(request, url, alias_model, template):
    """List all members in this group."""
    group_alias = get_object_or_404(alias_model, url=url)
    if group_alias.alias.url != url:
        return redirect('groups:show_group', url=group_alias.alias.url)

    is_curator = False
    is_manager = request.user.userprofile.is_manager
    is_pending = False
    show_delete_group_button = False
    membership_filter_form = forms.MembershipFilterForm(request.GET)

    group = group_alias.alias
    profile = request.user.userprofile
    in_group = group.has_member(profile)
    memberships = group.members.all()
    data = {}

    if isinstance(group, Group):
        # Has the user accepted the group terms
        if group.terms:
            membership = get_object_or_none(GroupMembership, group=group, userprofile=profile,
                                            status=GroupMembership.PENDING_TERMS)
            if membership:
                return redirect(reverse('groups:review_terms', args=[group.url]))

        # Is this user's membership pending?
        is_pending = group.has_pending_member(profile)

        is_curator = is_manager or (request.user.userprofile in group.curators.all())

        # initialize the form only when the group is moderated and user is curator of the group
        if is_curator and group.accepting_new_members == 'by_request':
            membership_filter_form = forms.MembershipFilterForm(request.GET)
        else:
            membership_filter_form = None

        if is_curator:
            statuses = [GroupMembership.MEMBER, GroupMembership.PENDING]
            if membership_filter_form and membership_filter_form.is_valid():
                filtr = membership_filter_form.cleaned_data['filtr']
                if filtr == 'members':
                    statuses = [GroupMembership.MEMBER]
                elif filtr == 'pending_members':
                    statuses = [GroupMembership.PENDING]

            memberships = group.groupmembership_set.filter(status__in=statuses)

            # Curators can delete their group if there are no other members.
            show_delete_group_button = is_curator and group.members.all().count() == 1

        else:
            # only show full members, or this user
            memberships = group.groupmembership_set.filter(
                Q(status=GroupMembership.MEMBER) | Q(userprofile=profile))

        # Order by UserProfile.Meta.ordering
        memberships = memberships.order_by('userprofile')

        # Find the most common skills of the group members.
        # Order by popularity in the group.
        shared_skill_ids = (group.members.filter(groupmembership__status=GroupMembership.MEMBER)
                            .values_list('skills', flat=True))

        count_skills = defaultdict(int)
        for skill_id in shared_skill_ids:
            count_skills[skill_id] += 1
        common_skills_ids = [k for k, v in sorted(count_skills.items(),
                                                  key=lambda x: x[1],
                                                  reverse=True)
                             if count_skills[k] > 1]

        # Translate ids to Skills preserving order.
        skills = [Skill.objects.get(id=skill_id) for skill_id in common_skills_ids if skill_id]

        data.update(skills=skills, membership_filter_form=membership_filter_form)

    page = request.GET.get('page', 1)
    paginator = Paginator(memberships, settings.ITEMS_PER_PAGE)

    try:
        people = paginator.page(page)
    except PageNotAnInteger:
        people = paginator.page(1)
    except EmptyPage:
        people = paginator.page(paginator.num_pages)

    show_pagination = paginator.count > settings.ITEMS_PER_PAGE

    extra_data = dict(
        people=people,
        group=group,
        in_group=in_group,
        is_curator=is_curator,
        is_pending=is_pending,
        show_pagination=show_pagination,
        show_delete_group_button=show_delete_group_button,
        show_join_button=group.user_can_join(request.user.userprofile),
        show_leave_button=group.user_can_leave(request.user.userprofile),
        members=group.member_count,
    )

    data.update(extra_data)

    return render(request, template, data)
Example #25
0
def show(request, url, alias_model, template):
    """List all members in this group."""
    group_alias = get_object_or_404(alias_model, url=url)
    if group_alias.alias.url != url:
        return redirect('groups:show_group', url=group_alias.alias.url)

    is_curator = False
    is_manager = request.user.userprofile.is_manager
    is_pending = False
    show_delete_group_button = False
    membership_filter_form = forms.MembershipFilterForm(request.GET)

    group = group_alias.alias
    profile = request.user.userprofile
    in_group = group.has_member(profile)
    memberships = group.members.all()
    data = {}

    if isinstance(group, Group):

        # Has the user accepted the group terms
        if group.terms:
            membership = get_object_or_none(
                GroupMembership,
                group=group,
                userprofile=profile,
                status=GroupMembership.PENDING_TERMS)
            if membership:
                return redirect(
                    reverse('groups:review_terms', args=[group.url]))

        # Is this user's membership pending?
        is_pending = group.has_pending_member(profile)

        is_curator = is_manager or (request.user.userprofile
                                    in group.curators.all())

        # initialize the form only when the group is moderated and user is curator of the group
        if (is_curator and (group.accepting_new_members == Group.REVIEWED
                            or group.accepting_new_members == Group.CLOSED)):
            membership_filter_form = forms.MembershipFilterForm(request.GET)
        else:
            membership_filter_form = None

        if is_curator:
            statuses = [
                GroupMembership.MEMBER, GroupMembership.PENDING,
                GroupMembership.PENDING_TERMS
            ]
            q_args = {'status__in': statuses}
            if membership_filter_form and membership_filter_form.is_valid():
                filtr = membership_filter_form.cleaned_data['filtr']
                if filtr == 'members':
                    statuses = [GroupMembership.MEMBER]
                elif filtr == 'pending_members':
                    statuses = [GroupMembership.PENDING]
                elif filtr == 'pending_terms':
                    statuses = [GroupMembership.PENDING_TERMS]

                q_args.update({'status__in': statuses})

                if filtr == 'needs_renewal':
                    q_args = {'needs_renewal': True}

            memberships = group.groupmembership_set.filter(**q_args)

            # Curators can delete their group if there are no other members.
            show_delete_group_button = is_curator and group.members.all(
            ).count() == 1

        else:
            # only show full members, or this user
            memberships = group.groupmembership_set.filter(
                Q(status=GroupMembership.MEMBER) | Q(userprofile=profile))

        invitation = get_object_or_none(Invite,
                                        redeemer=profile,
                                        group=group,
                                        accepted=False)
        data.update(invitation=invitation)
        # Order by UserProfile.Meta.ordering
        memberships = memberships.order_by('userprofile')

        # Find the most common skills of the group members.
        # Order by popularity in the group.
        shared_skill_ids = (group.members.filter(
            groupmembership__status=GroupMembership.MEMBER).values_list(
                'skills', flat=True))

        count_skills = defaultdict(int)
        for skill_id in shared_skill_ids:
            count_skills[skill_id] += 1
        common_skills_ids = [
            k for k, _ in sorted(
                count_skills.items(), key=lambda x: x[1], reverse=True)
            if count_skills[k] > 1
        ]

        # Translate ids to Skills preserving order.
        skills = [
            Skill.objects.get(id=skill_id) for skill_id in common_skills_ids
            if skill_id
        ]

        data.update(skills=skills,
                    membership_filter_form=membership_filter_form)

    page = request.GET.get('page', 1)
    paginator = Paginator(memberships, settings.ITEMS_PER_PAGE)

    try:
        people = paginator.page(page)
    except PageNotAnInteger:
        people = paginator.page(1)
    except EmptyPage:
        people = paginator.page(paginator.num_pages)

    show_pagination = paginator.count > settings.ITEMS_PER_PAGE

    extra_data = dict(
        people=people,
        group=group,
        in_group=in_group,
        is_curator=is_curator,
        is_pending=is_pending,
        show_pagination=show_pagination,
        show_delete_group_button=show_delete_group_button,
        show_join_button=group.user_can_join(request.user.userprofile),
        show_leave_button=group.user_can_leave(request.user.userprofile),
        members=group.member_count,
    )

    data.update(extra_data)

    return render(request, template, data)
Example #26
0
def view_profile(request, username):
    """View a profile by username."""
    data = {}
    privacy_mappings = {'anonymous': PUBLIC, 'mozillian': MOZILLIANS, 'employee': EMPLOYEES,
                        'privileged': PRIVILEGED, 'myself': None}
    privacy_level = None
    abuse_form = None

    if (request.user.is_authenticated() and request.user.username == username):
        # own profile
        view_as = request.GET.get('view_as', 'myself')
        privacy_level = privacy_mappings.get(view_as, None)
        profile = UserProfile.objects.privacy_level(privacy_level).get(user__username=username)
        data['privacy_mode'] = view_as
    else:
        userprofile_query = UserProfile.objects.filter(user__username=username)
        public_profile_exists = userprofile_query.public().exists()
        profile_exists = userprofile_query.exists()
        profile_complete = userprofile_query.exclude(full_name='').exists()

        if not public_profile_exists:
            if not request.user.is_authenticated():
                # you have to be authenticated to continue
                messages.warning(request, LOGIN_MESSAGE)
                return (login_required(view_profile, login_url=reverse('phonebook:home'))
                        (request, username))

            if not request.user.userprofile.is_vouched:
                # you have to be vouched to continue
                messages.error(request, GET_VOUCHED_MESSAGE)
                return redirect('phonebook:home')

        if not profile_exists or not profile_complete:
            raise Http404

        profile = UserProfile.objects.get(user__username=username)
        profile.set_instance_privacy_level(PUBLIC)
        if request.user.is_authenticated():
            profile.set_instance_privacy_level(
                request.user.userprofile.privacy_level)

        if (request.user.is_authenticated() and request.user.userprofile.is_vouched and
                not profile.can_vouch):
            abuse_report = get_object_or_none(AbuseReport, reporter=request.user.userprofile,
                                              profile=profile)

            if not abuse_report:
                abuse_report = AbuseReport(reporter=request.user.userprofile, profile=profile)

            abuse_form = forms.AbuseReportForm(request.POST or None, instance=abuse_report)
            if abuse_form.is_valid():
                abuse_form.save()
                msg = _(u'Thanks for helping us improve mozillians.org!')
                messages.info(request, msg)
                return redirect('phonebook:profile_view', profile.user.username)

        if (request.user.is_authenticated() and profile.is_vouchable(request.user.userprofile)):

            vouch_form = forms.VouchForm(request.POST or None)
            data['vouch_form'] = vouch_form
            if vouch_form.is_valid():
                # We need to re-fetch profile from database.
                profile = UserProfile.objects.get(user__username=username)
                profile.vouch(request.user.userprofile, vouch_form.cleaned_data['description'])
                # Notify the current user that they vouched successfully.
                msg = _(u'Thanks for vouching for a fellow Mozillian! This user is now vouched!')
                messages.info(request, msg)
                return redirect('phonebook:profile_view', profile.user.username)

    data['shown_user'] = profile.user
    data['profile'] = profile
    data['groups'] = profile.get_annotated_groups()
    data['abuse_form'] = abuse_form

    # Only show pending groups if user is looking at their own profile,
    # or current user is a superuser
    if not (request.user.is_authenticated() and
            (request.user.username == username or request.user.is_superuser)):
        data['groups'] = [grp for grp in data['groups'] if not grp.pending]

    return render(request, 'phonebook/profile.html', data)
Example #27
0
    def get(self, request):
        """Callback handler for OIDC authorization code flow.

        This is based on the mozilla-django-oidc library.
        This callback is used to verify the identity added by the user.
        Users are already logged in and we do not care about authentication.
        The JWT token is used to prove the identity of the user.
        """

        profile = request.user.userprofile
        # This is a difference nonce than the one used to login!
        nonce = request.session.get('oidc_verify_nonce')
        if nonce:
            # Make sure that nonce is not used twice
            del request.session['oidc_verify_nonce']

        # Check for all possible errors and display a message to the user.
        errors = [
            'code' not in request.GET, 'state' not in request.GET,
            'oidc_verify_state' not in request.session,
            request.GET['state'] != request.session['oidc_verify_state']
        ]
        if any(errors):
            msg = 'Something went wrong, account verification failed.'
            messages.error(request, msg)
            return redirect('phonebook:profile_edit')

        token_payload = {
            'client_id':
            self.OIDC_RP_VERIFICATION_CLIENT_ID,
            'client_secret':
            self.OIDC_RP_VERIFICATION_CLIENT_SECRET,
            'grant_type':
            'authorization_code',
            'code':
            request.GET['code'],
            'redirect_uri':
            absolutify(self.request,
                       nonprefixed_url('phonebook:verify_identity_callback')),
        }
        response = requests.post(self.OIDC_OP_TOKEN_ENDPOINT,
                                 data=token_payload,
                                 verify=import_from_settings(
                                     'OIDC_VERIFY_SSL', True))
        response.raise_for_status()
        token_response = response.json()
        id_token = token_response.get('id_token')

        # Verify JWT
        jws = JWS.from_compact(force_bytes(id_token))
        jwk = JWK.load(smart_bytes(self.OIDC_RP_VERIFICATION_CLIENT_SECRET))
        verified_token = None
        if jws.verify(jwk):
            verified_token = jws.payload

        # Create the new Identity Profile.
        if verified_token:
            user_info = json.loads(verified_token)
            email = user_info['email']

            if not user_info.get('email_verified'):
                msg = 'Account verification failed: Email is not verified.'
                messages.error(request, msg)
                return redirect('phonebook:profile_edit')

            user_q = {'auth0_user_id': user_info['sub'], 'email': email}

            # If we are linking GitHub we need to save
            # the username too.
            if 'github|' in user_info['sub']:
                user_q['username'] = user_info['nickname']

            # Check that the identity doesn't exist in another Identity profile
            # or in another mozillians profile
            error_msg = ''
            if IdpProfile.objects.filter(**user_q).exists():
                error_msg = 'Account verification failed: Identity already exists.'
            elif User.objects.filter(email__iexact=email).exclude(
                    pk=profile.user.pk).exists():
                error_msg = 'The email in this identity is used by another user.'
            if error_msg:
                messages.error(request, error_msg)
                next_url = self.request.session.get('oidc_login_next', None)
                return HttpResponseRedirect(
                    next_url or reverse('phonebook:profile_edit'))

            # Save the new identity to the IdpProfile
            user_q['profile'] = profile
            idp, created = IdpProfile.objects.get_or_create(**user_q)

            current_idp = get_object_or_none(IdpProfile,
                                             profile=profile,
                                             primary=True)
            # The new identity is stronger than the one currently used. Let's swap
            append_msg = ''
            # We need to check for equality too in the case a user updates the primary email in
            # the same identity (matching auth0_user_id). If there is an addition of the same type
            # we are not swapping login identities
            if ((current_idp and current_idp.type < idp.type)
                    or (current_idp
                        and current_idp.auth0_user_id == idp.auth0_user_id)
                    or (not current_idp and created
                        and idp.type >= IdpProfile.PROVIDER_GITHUB)):
                IdpProfile.objects.filter(profile=profile).exclude(
                    pk=idp.pk).update(primary=False)
                idp.primary = True
                idp.save()
                # Also update the primary email of the user
                update_email_in_basket(profile.user.email, idp.email)
                User.objects.filter(pk=profile.user.id).update(email=idp.email)
                append_msg = ' You need to use this identity the next time you will login.'

            send_userprofile_to_cis.delay(profile.pk)
            if created:
                msg = 'Account successfully verified.'
                if append_msg:
                    msg += append_msg
                messages.success(request, msg)
            else:
                msg = 'Account verification failed: Identity already exists.'
                messages.error(request, msg)

        next_url = self.request.session.get('oidc_login_next', None)
        return HttpResponseRedirect(next_url
                                    or reverse('phonebook:profile_edit'))
Example #28
0
def edit_profile(request):
    """Edit user profile view."""
    # Don't use request.user
    user = User.objects.get(pk=request.user.id)
    profile = user.userprofile
    user_groups = profile.groups.all().order_by('name')
    idp_profiles = IdpProfile.objects.filter(profile=profile)
    idp_primary_profile = get_object_or_none(IdpProfile,
                                             profile=profile,
                                             primary=True)
    # The accounts that a user can select as the primary login identity
    accounts_qs = ExternalAccount.objects.exclude(
        type=ExternalAccount.TYPE_EMAIL)

    sections = {
        'registration_section': ['user_form', 'registration_form'],
        'basic_section': ['user_form', 'basic_information_form'],
        'groups_section': ['groups_privacy_form'],
        'skills_section': ['skills_form'],
        'idp_section': ['idp_profile_formset'],
        'languages_section': ['language_privacy_form', 'language_formset'],
        'accounts_section': ['accounts_formset'],
        'location_section': ['location_form'],
        'irc_section': ['irc_form'],
        'contribution_section': ['contribution_form'],
        'tshirt_section': ['tshirt_form'],
    }

    curr_sect = next((s for s in sections.keys() if s in request.POST), None)

    def get_request_data(form):
        if curr_sect and form in sections[curr_sect]:
            return request.POST
        return None

    ctx = {}
    ctx['user_form'] = forms.UserForm(get_request_data('user_form'),
                                      instance=user)
    ctx['registration_form'] = forms.RegisterForm(
        get_request_data('registration_form'),
        request.FILES or None,
        instance=profile)
    basic_information_data = get_request_data('basic_information_form')
    ctx['basic_information_form'] = forms.BasicInformationForm(
        basic_information_data, request.FILES or None, instance=profile)
    ctx['accounts_formset'] = forms.AccountsFormset(
        get_request_data('accounts_formset'),
        instance=profile,
        queryset=accounts_qs)
    ctx['location_form'] = forms.LocationForm(
        get_request_data('location_form'), instance=profile)
    ctx['language_formset'] = forms.LanguagesFormset(
        get_request_data('language_formset'),
        instance=profile,
        locale=request.locale)
    language_privacy_data = get_request_data('language_privacy_form')
    ctx['language_privacy_form'] = forms.LanguagesPrivacyForm(
        language_privacy_data, instance=profile)
    ctx['skills_form'] = forms.SkillsForm(get_request_data('skills_form'),
                                          instance=profile)
    ctx['contribution_form'] = forms.ContributionForm(
        get_request_data('contribution_form'), instance=profile)
    ctx['tshirt_form'] = forms.TshirtForm(get_request_data('tshirt_form'),
                                          instance=profile)
    ctx['groups_privacy_form'] = forms.GroupsPrivacyForm(
        get_request_data('groups_privacy_form'), instance=profile)
    ctx['irc_form'] = forms.IRCForm(get_request_data('irc_form'),
                                    instance=profile)
    ctx['idp_profile_formset'] = forms.IdpProfileFormset(
        get_request_data('idp_profile_formset'),
        instance=profile,
        queryset=idp_profiles)
    ctx['idp_primary_profile'] = idp_primary_profile

    ctx['autocomplete_form_media'] = ctx['registration_form'].media + ctx[
        'skills_form'].media
    forms_valid = True
    if request.POST:
        if not curr_sect:
            raise Http404
        curr_forms = map(lambda x: ctx[x], sections[curr_sect])
        forms_valid = all(map(lambda x: x.is_valid(), curr_forms))
        if forms_valid:
            old_username = request.user.username
            for f in curr_forms:
                f.save()

            # Spawn task to check for spam
            if not profile.is_vouched:
                x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
                if x_forwarded_for:
                    user_ip = x_forwarded_for.split(',')[0]
                else:
                    user_ip = request.META.get('REMOTE_ADDR')

                params = {
                    'instance_id': profile.id,
                    'user_ip': user_ip,
                    'user_agent': request.META.get('HTTP_USER_AGENT'),
                    'referrer': request.META.get('HTTP_REFERER'),
                    'comment_author': profile.full_name,
                    'comment_author_email': profile.email,
                    'comment_content': profile.bio
                }

                check_spam_account.delay(**params)

            next_section = request.GET.get('next')
            next_url = urlparams(reverse('phonebook:profile_edit'),
                                 next_section)
            if curr_sect == 'registration_section':
                settings_url = reverse('phonebook:profile_edit')
                settings_link = '<a href="{0}">settings</a>'.format(
                    settings_url)
                msg = _(u'Your registration is complete. '
                        u'Feel free to visit the {0} page to add '
                        u'additional information to your profile.'.format(
                            settings_link))
                messages.info(request, mark_safe(msg))
                redeem_invite(profile, request.session.get('invite-code'))
                next_url = reverse('phonebook:profile_view',
                                   args=[user.username])
            elif user.username != old_username:
                msg = _(u'You changed your username; '
                        u'please note your profile URL has also changed.')
                messages.info(request, _(msg))
            return HttpResponseRedirect(next_url)

    ctx.update({
        'user_groups': user_groups,
        'profile': request.user.userprofile,
        'vouch_threshold': settings.CAN_VOUCH_THRESHOLD,
        'appsv2': profile.apps.filter(enabled=True),
        'forms_valid': forms_valid
    })

    return render(request, 'phonebook/edit_profile.html', ctx)
Example #29
0
def notify_membership_renewal():
    """
    For groups with defined `invalidation_days` we need to notify users
    2 weeks prior invalidation that the membership is expiring.
    """

    from mozillians.groups.models import Group, GroupMembership, Invite

    groups = (Group.objects.filter(invalidation_days__isnull=False,
                                   invalidation_days__gte=DAYS_BEFORE_INVALIDATION)
                           .exclude(accepting_new_members=Group.OPEN).distinct())

    for group in groups:
        curator_ids = group.curators.all().values_list('id', flat=True)
        memberships = (group.groupmembership_set.filter(status=GroupMembership.MEMBER)
                       .exclude(userprofile__id__in=curator_ids))

        # Filter memberships to be notified
        # Switch is being used only for testing mail notifications
        # It disables membership filtering based on date
        if not switch_is_active('test_membership_renewal_notification'):
            last_update_days = group.invalidation_days - DAYS_BEFORE_INVALIDATION
            last_update = now() - timedelta(days=last_update_days)

            query_start = datetime.combine(last_update.date(), datetime.min.time())
            query_end = datetime.combine(last_update.date(), datetime.max.time())

            query = {
                'updated_on__range': [query_start, query_end],
                'needs_renewal': False,
            }
            memberships = memberships.filter(**query)

        member_template = get_template('groups/email/notify_member_renewal.txt')
        curator_template = get_template('groups/email/notify_curator_renewal.txt')

        for membership in memberships:
            ctx = {
                'member_full_name': membership.userprofile.full_name,
                'group_name': membership.group.name,
                'group_url': membership.group.get_absolute_url(),
                'member_profile_url': membership.userprofile.get_absolute_url(),
                'inviter': None
            }

            invite = get_object_or_none(Invite, group=group, redeemer=membership.userprofile)
            if invite:
                ctx['inviter'] = invite.inviter

            subject_msg = unicode('[Mozillians] Your membership to Mozilla group "{0}" '
                                  'is about to expire')
            subject = _(subject_msg.format(membership.group.name))
            message = member_template.render(ctx)
            send_mail(subject, message, settings.FROM_NOREPLY, [membership.userprofile.email])

            subject_msg = unicode('[Mozillians][{0}] Membership of "{1}" is about to expire')
            format_args = [membership.group.name, membership.userprofile.full_name]
            subject = _(subject_msg.format(*format_args))

            # In case the membership was created after an invitation we notify inviters only
            # Else we fallback to all group curators
            curators = group.curators.all()
            inviter = ctx['inviter']
            if inviter and curators.filter(pk=inviter.id).exists():
                curators = [inviter]

            for curator in curators:
                ctx['curator_full_name'] = curator.full_name
                message = curator_template.render(ctx)
                send_mail(subject, message, settings.FROM_NOREPLY, [curator.email])

        # Mark these memberships ready for an early renewal
        memberships.update(needs_renewal=True)
Example #30
0
def notify_membership_renewal():
    """
    For groups with defined `invalidation_days` we need to notify users
    2 weeks prior invalidation that the membership is expiring.
    """

    from mozillians.groups.models import Group, GroupMembership, Invite

    groups = (Group.objects.filter(
        invalidation_days__isnull=False,
        invalidation_days__gte=DAYS_BEFORE_INVALIDATION).exclude(
            accepting_new_members=Group.OPEN).distinct())

    for group in groups:
        curator_ids = group.curators.all().values_list('id', flat=True)
        memberships = (group.groupmembership_set.filter(
            status=GroupMembership.MEMBER).exclude(
                userprofile__id__in=curator_ids))

        # Filter memberships to be notified
        # Switch is being used only for testing mail notifications
        # It disables membership filtering based on date
        if not switch_is_active('test_membership_renewal_notification'):
            last_update_days = group.invalidation_days - DAYS_BEFORE_INVALIDATION
            last_update = now() - timedelta(days=last_update_days)

            query_start = datetime.combine(last_update.date(),
                                           datetime.min.time())
            query_end = datetime.combine(last_update.date(),
                                         datetime.max.time())

            query = {
                'updated_on__range': [query_start, query_end],
                'needs_renewal': False,
            }
            memberships = memberships.filter(**query)

        member_template = get_template(
            'groups/email/notify_member_renewal.txt')
        curator_template = get_template(
            'groups/email/notify_curator_renewal.txt')

        for membership in memberships:
            ctx = {
                'member_full_name': membership.userprofile.full_name,
                'group_name': membership.group.name,
                'group_url': membership.group.get_absolute_url(),
                'member_profile_url':
                membership.userprofile.get_absolute_url(),
                'inviter': None
            }

            invite = get_object_or_none(Invite,
                                        group=group,
                                        redeemer=membership.userprofile)
            if invite:
                ctx['inviter'] = invite.inviter

            subject_msg = unicode(
                '[Mozillians] Your membership to Mozilla group "{0}" '
                'is about to expire')
            subject = _(subject_msg.format(membership.group.name))
            message = member_template.render(ctx)
            send_mail(subject, message, settings.FROM_NOREPLY,
                      [membership.userprofile.email])

            subject_msg = unicode(
                '[Mozillians][{0}] Membership of "{1}" is about to expire')
            format_args = [
                membership.group.name, membership.userprofile.full_name
            ]
            subject = _(subject_msg.format(*format_args))

            # In case the membership was created after an invitation we notify inviters only
            # Else we fallback to all group curators
            curators = group.curators.all()
            inviter = ctx['inviter']
            if inviter and curators.filter(pk=inviter.id).exists():
                curators = [inviter]

            for curator in curators:
                ctx['curator_full_name'] = curator.full_name
                message = curator_template.render(ctx)
                send_mail(subject, message, settings.FROM_NOREPLY,
                          [curator.email])

        # Mark these memberships ready for an early renewal
        memberships.update(needs_renewal=True)
Example #31
0
    def get(self, request):
        """Callback handler for OIDC authorization code flow.

        This is based on the mozilla-django-oidc library.
        This callback is used to verify the identity added by the user.
        Users are already logged in and we do not care about authentication.
        The JWT token is used to prove the identity of the user.
        """

        profile = request.user.userprofile
        # This is a difference nonce than the one used to login!
        nonce = request.session.get('oidc_verify_nonce')
        if nonce:
            # Make sure that nonce is not used twice
            del request.session['oidc_verify_nonce']

        # Check for all possible errors and display a message to the user.
        errors = [
            'code' not in request.GET,
            'state' not in request.GET,
            'oidc_verify_state' not in request.session,
            request.GET['state'] != request.session['oidc_verify_state']
        ]
        if any(errors):
            msg = 'Something went wrong, account verification failed.'
            messages.error(request, msg)
            return redirect('phonebook:profile_edit')

        token_payload = {
            'client_id': self.OIDC_RP_VERIFICATION_CLIENT_ID,
            'client_secret': self.OIDC_RP_VERIFICATION_CLIENT_SECRET,
            'grant_type': 'authorization_code',
            'code': request.GET['code'],
            'redirect_uri': absolutify(
                self.request,
                nonprefixed_url('phonebook:verify_identity_callback')
            ),
        }
        response = requests.post(self.OIDC_OP_TOKEN_ENDPOINT,
                                 data=token_payload,
                                 verify=import_from_settings('OIDC_VERIFY_SSL', True))
        response.raise_for_status()
        token_response = response.json()
        id_token = token_response.get('id_token')

        # Verify JWT
        jws = JWS.from_compact(force_bytes(id_token))
        jwk = JWK.load(smart_bytes(self.OIDC_RP_VERIFICATION_CLIENT_SECRET))
        verified_token = None
        if jws.verify(jwk):
            verified_token = jws.payload

        # Create the new Identity Profile.
        if verified_token:
            user_info = json.loads(verified_token)
            email = user_info['email']

            if not user_info.get('email_verified'):
                msg = 'Account verification failed: Email is not verified.'
                messages.error(request, msg)
                return redirect('phonebook:profile_edit')

            user_q = {
                'auth0_user_id': user_info['sub'],
                'email': email
            }

            # If we are linking GitHub we need to save
            # the username too.
            if 'github|' in user_info['sub']:
                user_q['username'] = user_info['nickname']

            # Check that the identity doesn't exist in another Identity profile
            # or in another mozillians profile
            error_msg = ''
            if IdpProfile.objects.filter(**user_q).exists():
                error_msg = 'Account verification failed: Identity already exists.'
            elif User.objects.filter(email__iexact=email).exclude(pk=profile.user.pk).exists():
                error_msg = 'The email in this identity is used by another user.'
            if error_msg:
                messages.error(request, error_msg)
                next_url = self.request.session.get('oidc_login_next', None)
                return HttpResponseRedirect(next_url or reverse('phonebook:profile_edit'))

            # Save the new identity to the IdpProfile
            user_q['profile'] = profile
            idp, created = IdpProfile.objects.get_or_create(**user_q)

            current_idp = get_object_or_none(IdpProfile, profile=profile, primary=True)
            # The new identity is stronger than the one currently used. Let's swap
            append_msg = ''
            # We need to check for equality too in the case a user updates the primary email in
            # the same identity (matching auth0_user_id). If there is an addition of the same type
            # we are not swapping login identities
            if ((current_idp and current_idp.type < idp.type) or
                (current_idp and current_idp.auth0_user_id == idp.auth0_user_id) or
                    (not current_idp and created and idp.type >= IdpProfile.PROVIDER_GITHUB)):
                IdpProfile.objects.filter(profile=profile).exclude(pk=idp.pk).update(primary=False)
                idp.primary = True
                idp.save()
                # Also update the primary email of the user
                update_email_in_basket(profile.user.email, idp.email)
                User.objects.filter(pk=profile.user.id).update(email=idp.email)
                append_msg = ' You need to use this identity the next time you will login.'

            send_userprofile_to_cis.delay(profile.pk)
            if created:
                msg = 'Account successfully verified.'
                if append_msg:
                    msg += append_msg
                messages.success(request, msg)
            else:
                msg = 'Account verification failed: Identity already exists.'
                messages.error(request, msg)

        next_url = self.request.session.get('oidc_login_next', None)
        return HttpResponseRedirect(next_url or reverse('phonebook:profile_edit'))