Example #1
0
    def test_change_email(self, basket_mock, lookup_mock, subscribe_mock,
                          unsubscribe_mock, switch_is_active_mock):

        # Create a new user
        old_email = '*****@*****.**'
        # We need vouched=False in order to avoid triggering a basket_update through signals.
        user = UserFactory.create(email=old_email, vouched=False).userprofile
        new_email = '*****@*****.**'

        # Enable basket.
        switch_is_active_mock.return_value = True

        # Mock all the calls to basket.
        basket_mock.lookup_user.return_value = {
            'email': old_email,  # the old value
            'newsletters': ['foo', 'bar']
        }
        basket_mock.unsubscribe.return_value = {
            'result': 'ok',
        }
        basket_mock.subscribe.return_value = {
            'token': 'new token',
        }

        lookup_mock.reset_mock()
        subscribe_mock.reset_mock()
        unsubscribe_mock.reset_mock()

        # When a user's email is changed, their old email is unsubscribed
        # from all newsletters related to mozillians.org and their new email is subscribed to them.
        update_email_in_basket(user.email, new_email)

        # Verify subtask calls and call count
        ok_(lookup_mock.subtask.called)
        eq_(lookup_mock.subtask.call_count, 1)
        ok_(subscribe_mock.subtask.called)
        eq_(subscribe_mock.subtask.call_count, 1)
        ok_(unsubscribe_mock.subtask.called)
        eq_(unsubscribe_mock.subtask.call_count, 1)

        # Verify call arguments
        lookup_mock.subtask.assert_called_with((user.email, ))
        unsubscribe_mock.subtask.called_with(({
            'token': 'new token',
            'email': '*****@*****.**',
            'newsletters': ['foo', 'bar']
        }, ))
        subscribe_mock.subtask.called_with(('*****@*****.**', ))
Example #2
0
    def test_change_email(self, basket_mock, lookup_mock, subscribe_mock, unsubscribe_mock,
                          switch_is_active_mock):

        # Create a new user
        old_email = '*****@*****.**'
        # We need vouched=False in order to avoid triggering a basket_update through signals.
        user = UserFactory.create(email=old_email, vouched=False).userprofile
        new_email = '*****@*****.**'

        # Enable basket.
        switch_is_active_mock.return_value = True

        # Mock all the calls to basket.
        basket_mock.lookup_user.return_value = {
            'email': old_email,  # the old value
            'newsletters': ['foo', 'bar']
        }
        basket_mock.unsubscribe.return_value = {
            'result': 'ok',
        }
        basket_mock.subscribe.return_value = {
            'token': 'new token',
        }

        lookup_mock.reset_mock()
        subscribe_mock.reset_mock()
        unsubscribe_mock.reset_mock()

        # When a user's email is changed, their old email is unsubscribed
        # from all newsletters related to mozillians.org and their new email is subscribed to them.
        update_email_in_basket(user.email, new_email)

        # Verify subtask calls and call count
        ok_(lookup_mock.subtask.called)
        eq_(lookup_mock.subtask.call_count, 1)
        ok_(subscribe_mock.subtask.called)
        eq_(subscribe_mock.subtask.call_count, 1)
        ok_(unsubscribe_mock.subtask.called)
        eq_(unsubscribe_mock.subtask.call_count, 1)

        # Verify call arguments
        lookup_mock.subtask.assert_called_with((user.email,))
        unsubscribe_mock.subtask.called_with(({'token': 'new token',
                                               'email': '*****@*****.**',
                                               'newsletters': ['foo', 'bar']},))
        subscribe_mock.subtask.called_with(('*****@*****.**',))
Example #3
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 #4
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'))