Esempio n. 1
0
 def test_get_or_create_user_exists(self):
     user = UserFactory()
     fetched, created = get_or_create_user(user.fullname, user.username,
                                           True)
     assert_false(created)
     assert_equal(user._id, fetched._id)
     assert_false('is_spam' in fetched.system_tags)
Esempio n. 2
0
    def authenticate(self, request):
        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256'
            )
        except (jwt.InvalidTokenError, TypeError):
            raise AuthenticationFailed

        # The JWT `data` payload is expected in the following structure.
        #
        # {"provider": {
        #     "idp": "https://login.circle.edu/idp/shibboleth",
        #     "id": "CIR",
        #     "user": {
        #         "middleNames": "",
        #         "familyName": "",
        #         "givenName": "",
        #         "fullname": "Circle User",
        #         "suffix": "",
        #         "username": "******"
        #     }
        # }}
        data = json.loads(payload['data'])
        provider = data['provider']

        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed('Invalid institution id specified "{}"'.format(provider['id']))

        username = provider['user']['username']
        fullname = provider['user']['fullname']

        user, created = get_or_create_user(fullname, username, reset_password=False)

        if created:
            user.given_name = provider['user'].get('givenName')
            user.middle_names = provider['user'].get('middleNames')
            user.family_name = provider['user'].get('familyName')
            user.suffix = provider['user'].get('suffix')
            user.date_last_login = datetime.utcnow()
            user.save()

            # User must be saved in order to have a valid _id
            user.register(username)
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                mimetype='html',
                user=user
            )

        if institution not in user.affiliated_institutions:
            user.affiliated_institutions.append(institution)
            user.save()

        return user, None
Esempio n. 3
0
    def authenticate(self, request):
        try:
            payload = jwt.decode(jwe.decrypt(request.body,
                                             settings.JWE_SECRET),
                                 settings.JWT_SECRET,
                                 options={'verify_exp': False},
                                 algorithm='HS256')
        except (jwt.InvalidTokenError, TypeError):
            raise AuthenticationFailed

        # The JWT `data` payload is expected in the following structure.
        #
        # {"provider": {
        #     "idp": "https://login.circle.edu/idp/shibboleth",
        #     "id": "CIR",
        #     "user": {
        #         "middleNames": "",
        #         "familyName": "",
        #         "givenName": "",
        #         "fullname": "Circle User",
        #         "suffix": "",
        #         "username": "******"
        #     }
        # }}
        data = json.loads(payload['data'])
        provider = data['provider']

        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed(
                'Invalid institution id specified "{}"'.format(provider['id']))

        username = provider['user']['username']
        fullname = provider['user']['fullname']

        user, created = get_or_create_user(fullname,
                                           username,
                                           reset_password=False)

        if created:
            user.given_name = provider['user'].get('givenName')
            user.middle_names = provider['user'].get('middleNames')
            user.family_name = provider['user'].get('familyName')
            user.suffix = provider['user'].get('suffix')
            user.date_last_login = datetime.utcnow()
            user.save()

            # User must be saved in order to have a valid _id
            user.register(username)
            send_mail(to_addr=user.username,
                      mail=WELCOME_OSF4I,
                      mimetype='html',
                      user=user)

        if institution not in user.affiliated_institutions:
            user.affiliated_institutions.append(institution)
            user.save()

        return user, None
Esempio n. 4
0
 def test_get_or_create_user_is_spam(self):
     fullname = 'John Deacon'
     username = '******'
     fetched, created = get_or_create_user(fullname, username, is_spam=True)
     assert_true(created)
     assert_equal(fetched.fullname, fullname)
     assert_equal(fetched.username, username)
     assert_true('is_spam' in fetched.system_tags)
Esempio n. 5
0
 def test_get_or_create_user_not_exists(self):
     fullname = 'Roger Taylor'
     username = '******'
     fetched, created = get_or_create_user(fullname, username, is_spam=False)
     assert_true(created)
     assert_equal(fetched.fullname, fullname)
     assert_equal(fetched.username, username)
     assert_false('is_spam' in fetched.system_tags)
Esempio n. 6
0
 def test_get_or_create_user_not_exists(self):
     fullname = 'Roger Taylor'
     username = '******'
     fetched, created = get_or_create_user(fullname, username, False)
     assert_true(created)
     assert_equal(fetched.fullname, fullname)
     assert_equal(fetched.username, username)
     assert_false('is_spam' in fetched.system_tags)
Esempio n. 7
0
 def test_get_or_create_user_is_spam(self):
     fullname = 'John Deacon'
     username = '******'
     fetched, created = get_or_create_user(fullname, username, True)
     assert_true(created)
     assert_equal(fetched.fullname, fullname)
     assert_equal(fetched.username, username)
     assert_true('is_spam' in fetched.system_tags)
Esempio n. 8
0
 def test_get_or_create_user_not_exists(self):
     fullname = 'Roger Taylor'
     username = '******'
     fetched, created = get_or_create_user(fullname, username, is_spam=False)
     fetched.save()  # in order to access m2m fields, e.g. tags
     assert_true(created)
     assert_equal(fetched.fullname, fullname)
     assert_equal(fetched.username, username)
     assert_false('is_spam' in fetched.system_tags)
 def test_get_or_create_user_is_spam(self):
     fullname = 'John Deacon'
     username = '******'
     fetched, created = get_or_create_user(fullname, username, is_spam=True)
     fetched.save()  # in order to access m2m fields, e.g. tags
     assert_true(created)
     assert_equal(fetched.fullname, fullname)
     assert_equal(fetched.username, username)
     assert_true('is_spam' in fetched.system_tags)
Esempio n. 10
0
 def test_get_or_create_user_is_spam(self):
     fullname = 'John Deacon'
     username = '******'
     fetched, created = get_or_create_user(fullname, username, is_spam=True)
     fetched.save()  # in order to access m2m fields, e.g. tags
     assert_true(created)
     assert_equal(fetched.fullname, fullname)
     assert_equal(fetched.username, username)
     assert_true('is_spam' in fetched.system_tags)
Esempio n. 11
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(
            message.sender_email,
            CONFERENCE_FAILED,
            fullname=message.sender_display,
        )

    nodes_created = []
    users_created = []

    with transaction.atomic():
        user, user_created = get_or_create_user(
            message.sender_display,
            message.sender_email,
            is_spam=message.is_spam,
        )
        if user_created:
            user.save(
            )  # need to save in order to access m2m fields (e.g. tags)
            users_created.append(user)
            user.add_system_tag('osf4m')
            user.update_date_last_login()
            user.save()

            # must save the user first before accessing user._id
            set_password_url = web_url_for(
                'reset_password_get',
                uid=user._id,
                token=user.verification_key_v2['token'],
                _absolute=True,
            )
        else:
            set_password_url = None

        node, node_created = Node.objects.get_or_create(
            title__iexact=message.subject,
            is_deleted=False,
            _contributors__guids___id=user._id,
            defaults={
                'title': message.subject,
                'creator': user
            })
        if node_created:
            nodes_created.append(node)
            node.add_system_tag('osf4m')
            node.save()

        utils.provision_node(conference, message, node, user)
        utils.record_message(message, nodes_created, users_created)
    # Prevent circular import error
    from framework.auth import signals as auth_signals
    if user_created:
        auth_signals.user_confirmed.send(user)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        'addon_view_or_download_file',
        path=message.attachments[0].filename,
        provider='osfstorage',
        action='download',
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for(
            'conference_results',
            meeting=message.conference_name,
            _absolute=True,
        ),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
    )
    if node_created and user_created:
        signals.osf4m_user_created.send(user, conference=conference, node=node)
Esempio n. 12
0
    def authenticate(self, request):
        """
        Handle CAS institution authentication request.

        The JWT `data` payload is expected in the following structure:
        {
            "provider": {
                "idp":  "",
                "id":   "",
                "user": {
                    "username":     "",
                    "fullname":     "",
                    "familyName":   "",
                    "givenName":    "",
                    "middleNames":  "",
                    "suffix":       "",
                }
            }
        }

        :param request: the POST request
        :return: user, None if authentication succeed
        :raises: AuthenticationFailed if authentication fails
        """

        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256',
            )
        except (jwt.InvalidTokenError, TypeError,
                jwe.exceptions.MalformedData):
            raise AuthenticationFailed

        data = json.loads(payload['data'])
        provider = data['provider']

        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed(
                'Invalid institution id specified "{}"'.format(provider['id']))

        username = provider['user'].get('username')
        fullname = provider['user'].get('fullname')
        given_name = provider['user'].get('givenName')
        family_name = provider['user'].get('familyName')
        middle_names = provider['user'].get('middleNames')
        suffix = provider['user'].get('suffix')

        # use given name and family name to build full name if not provided
        if given_name and family_name and not fullname:
            fullname = given_name + ' ' + family_name

        # institution must provide `fullname`, otherwise we fail the authentication and inform sentry
        if not fullname:
            message = 'Institution login failed: fullname required' \
                      ' for user {} from institution {}'.format(username, provider['id'])
            sentry.log_message(message)
            raise AuthenticationFailed(message)

        # `get_or_create_user()` guesses names from fullname
        # replace the guessed ones if the names are provided from the authentication
        user, created = get_or_create_user(fullname,
                                           username,
                                           reset_password=False)
        if created:
            if given_name:
                user.given_name = given_name
            if family_name:
                user.family_name = family_name
            if middle_names:
                user.middle_names = middle_names
            if suffix:
                user.suffix = suffix
            user.update_date_last_login()

            # Relying on front-end validation until `accepted_tos` is added to the JWT payload
            user.accepted_terms_of_service = timezone.now()

            # save and register user
            user.save()
            user.register(username)

            # send confirmation email
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                mimetype='html',
                user=user,
                domain=DOMAIN,
                osf_support_email=OSF_SUPPORT_EMAIL,
                storage_flag_is_active=waffle.flag_is_active(
                    request, features.STORAGE_I18N),
            )

        if not user.is_affiliated_with_institution(institution):
            user.affiliated_institutions.add(institution)
            user.save()

        return user, None
 def test_get_or_create_user_exists(self):
     user = UserFactory()
     fetched, created = get_or_create_user(user.fullname, user.username, is_spam=True)
     assert_false(created)
     assert_equal(user._id, fetched._id)
     assert_false('is_spam' in fetched.system_tags)
Esempio n. 14
0
    def authenticate(self, request):
        """
        Handle CAS institution authentication request.

        The JWT `data` payload is expected in the following structure:
        {
            "provider": {
                "idp": "",
                "id": "",
                "user": {
                    "username": "",
                    "fullname": "",
                    "familyName": "",
                    "givenName": "",
                    "middleNames": "",
                    "suffix": "",
                    "department": "",
                    "isMemberOf": "",  # Shared SSO
                    "selectiveSsoFilter": "",  # Selective SSO
                }
            }
        }

        Note that if authentication failed, HTTP 403 Forbidden is returned no matter what type of
        exception is raised. In this method, we use `AuthenticationFailed` when the payload is not
        correctly encrypted/encoded since it is the "authentication" between CAS and this endpoint.
        We use `PermissionDenied` for all other exceptions that happened afterwards.

        :param request: the POST request
        :return: user, None if authentication succeed
        :raises: AuthenticationFailed or PermissionDenied if authentication fails
        """

        # Verify / decrypt / decode the payload
        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256',
            )
        except (jwt.InvalidTokenError, TypeError,
                jwe.exceptions.MalformedData):
            raise AuthenticationFailed(
                detail='InstitutionSsoRequestNotAuthorized')

        # Load institution and user data
        data = json.loads(payload['data'])
        provider = data['provider']
        institution = Institution.load(provider['id'])
        if not institution:
            message = 'Institution SSO Error: invalid institution ID [{}]'.format(
                provider['id'])
            logger.error(message)
            sentry.log_message(message)
            raise PermissionDenied(detail='InstitutionSsoInvalidInstitution')
        username = provider['user'].get('username')
        fullname = provider['user'].get('fullname')
        given_name = provider['user'].get('givenName')
        family_name = provider['user'].get('familyName')
        middle_names = provider['user'].get('middleNames')
        suffix = provider['user'].get('suffix')
        department = provider['user'].get('department')
        selective_sso_filter = provider['user'].get('selectiveSsoFilter')

        # Check selective login first
        if provider['id'] in INSTITUTION_SELECTIVE_SSO_MAP:
            if selective_sso_filter != INSTITUTION_SELECTIVE_SSO_MAP[
                    provider['id']]:
                message = f'Institution SSO Error: user [email={username}] is not allowed for ' \
                          f'institution SSO [id={institution._id}] due to selective SSO rules'
                logger.error(message)
                sentry.log_message(message)
                raise PermissionDenied(
                    detail='InstitutionSsoSelectiveNotAllowed')
            logger.info(
                f'Institution SSO: selective SSO verified for user [email={username}] '
                f'at institution [id={institution._id}]', )

        # Check secondary institutions which uses the SSO of primary ones
        secondary_institution = None
        if provider['id'] in INSTITUTION_SHARED_SSO_MAP:
            switch_map = INSTITUTION_SHARED_SSO_MAP[provider['id']]
            criteria_type = switch_map.get('criteria')
            if criteria_type == 'attribute':
                attribute_name = switch_map.get('attribute')
                attribute_value = provider['user'].get(attribute_name)
                if attribute_value:
                    secondary_institution_id = switch_map.get(
                        'institutions',
                        {},
                    ).get(attribute_value)
                    logger.info(
                        'Institution SSO: primary=[{}], secondary=[{}], '
                        'username=[{}]'.format(provider['id'],
                                               secondary_institution_id,
                                               username))
                    secondary_institution = Institution.load(
                        secondary_institution_id)
                    if not secondary_institution:
                        # Log errors and inform Sentry but do not raise an exception if OSF fails
                        # to load the secondary institution from database
                        message = 'Institution SSO Error: invalid secondary institution [{}]; ' \
                                  'primary=[{}], username=[{}]'.format(attribute_value, provider['id'], username)
                        logger.error(message)
                        sentry.log_message(message)
                else:
                    # SSO from primary institution only
                    logger.info(
                        'Institution SSO: primary=[{}], secondary=[None], '
                        'username=[{}]'.format(provider['id'], username))
            else:
                message = 'Institution SSO Error: invalid criteria [{}]; ' \
                          'primary=[{}], username=[{}]'.format(criteria_type, provider['id'], username)
                logger.error(message)
                sentry.log_message(message)

        # Use given name and family name to build full name if it is not provided
        if given_name and family_name and not fullname:
            fullname = given_name + ' ' + family_name

        # Non-empty full name is required. Fail the auth and inform sentry if not provided.
        if not fullname:
            message = 'Institution SSO Error: missing fullname ' \
                      'for user [{}] from institution [{}]'.format(username, provider['id'])
            logger.error(message)
            sentry.log_message(message)
            raise PermissionDenied(detail='InstitutionSsoMissingUserNames')

        # Get an existing user or create a new one. If a new user is created, the user object is
        # confirmed but not registered,which is temporarily of an inactive status. If an existing
        # user is found, it is also possible that the user is inactive (e.g. unclaimed, disabled,
        # unconfirmed, etc.).
        user, created = get_or_create_user(fullname,
                                           username,
                                           reset_password=False)

        # Existing but inactive users need to be either "activated" or failed the auth
        activation_required = False
        new_password_required = False
        if not created:
            try:
                drf.check_user(user)
                logger.info(
                    'Institution SSO: active user [{}]'.format(username))
            except exceptions.UnclaimedAccountError:
                # Unclaimed user (i.e. a user that has been added as an unregistered contributor)
                user.unclaimed_records = {}
                activation_required = True
                # Unclaimed users have an unusable password when being added as an unregistered
                # contributor. Thus a random usable password must be assigned during activation.
                new_password_required = True
                logger.warning(
                    'Institution SSO: unclaimed contributor [{}]'.format(
                        username))
            except exceptions.UnconfirmedAccountError:
                if user.has_usable_password():
                    # Unconfirmed user from default username / password signup
                    user.email_verifications = {}
                    activation_required = True
                    # Unconfirmed users already have a usable password set by the creator during
                    # sign-up. However, it must be overwritten by a new random one so the creator
                    # (if he is not the real person) can not access the account after activation.
                    new_password_required = True
                    logger.warning(
                        'Institution SSO: unconfirmed user [{}]'.format(
                            username))
                else:
                    # Login take-over has not been implemented for unconfirmed user created via
                    # external IdP login (ORCiD).
                    message = 'Institution SSO Error: SSO is not eligible for an unconfirmed account [{}] ' \
                              'created via IdP login'.format(username)
                    sentry.log_message(message)
                    logger.error(message)
                    raise PermissionDenied(
                        detail='InstitutionSsoAccountNotConfirmed')
            except exceptions.DeactivatedAccountError:
                # Deactivated user: login is not allowed for deactivated users
                message = 'Institution SSO Error: SSO is not eligible for a deactivated account: [{}]'.format(
                    username)
                sentry.log_message(message)
                logger.error(message)
                raise PermissionDenied(detail='InstitutionSsoAccountDisabled')
            except exceptions.MergedAccountError:
                # Merged user: this shouldn't happen since merged users do not have an email
                message = 'Institution SSO Error: SSO is not eligible for a merged account: [{}]'.format(
                    username)
                sentry.log_message(message)
                logger.error(message)
                raise PermissionDenied(detail='InstitutionSsoAccountMerged')
            except exceptions.InvalidAccountError:
                # Other invalid status: this shouldn't happen unless the user happens to be in a
                # temporary state. Such state requires more updates before the user can be saved
                # to the database. (e.g. `get_or_create_user()` creates a temporary-state user.)
                message = 'Institution SSO Error: SSO is not eligible for an inactive account [{}] ' \
                          'with an unknown or invalid status'.format(username)
                sentry.log_message(message)
                logger.error(message)
                raise PermissionDenied(detail='InstitutionSsoInvalidAccount')
        else:
            logger.info('Institution SSO: new user [{}]'.format(username))

        # The `department` field is updated each login when it was changed.
        user_guid = user.guids.first()._id
        if department:
            if user.department != department:
                user.department = department
                user.save()
            logger.info(
                'Institution SSO: user w/ dept: user=[{}], email=[{}], inst=[{}], '
                'dept=[{}]'.format(user_guid, username, institution._id,
                                   department))
        else:
            logger.info(
                'Institution SSO: user w/o dept: user=[{}], email=[{}], '
                'inst=[{}]'.format(user_guid, username, institution._id))

        # Both created and activated accounts need to be updated and registered
        if created or activation_required:

            if given_name:
                user.given_name = given_name
            if family_name:
                user.family_name = family_name
            if middle_names:
                user.middle_names = middle_names
            if suffix:
                user.suffix = suffix

            # Users claimed or confirmed via institution SSO should have their full name updated
            if activation_required:
                user.fullname = fullname

            user.update_date_last_login()

            # Register and save user
            password = str(uuid.uuid4()) if new_password_required else None
            user.register(username, password=password)
            user.save()

            # Send confirmation email for all three: created, confirmed and claimed
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                user=user,
                domain=DOMAIN,
                osf_support_email=OSF_SUPPORT_EMAIL,
                storage_flag_is_active=waffle.flag_is_active(
                    request, features.STORAGE_I18N),
            )

        # Affiliate the user to the primary institution if not previously affiliated
        if not user.is_affiliated_with_institution(institution):
            user.affiliated_institutions.add(institution)
            user.save()

        # Affiliate the user to the secondary institution if not previously affiliated
        if secondary_institution and not user.is_affiliated_with_institution(
                secondary_institution):
            user.affiliated_institutions.add(secondary_institution)
            user.save()

        return user, None
 def test_get_or_create_user_with_blacklisted_domain(self):
     fullname = 'Kanye West'
     username = '******'
     with assert_raises(BlacklistedEmailError) as e:
         get_or_create_user(fullname, username, is_spam=True)
     assert_equal(str(e.exception), 'Invalid Email')
Esempio n. 16
0
    def dispatch(self, request, *args, **kwargs):

        eppn = request.environ['HTTP_AUTH_EPPN']
        seps = eppn.split(SHIB_EPPN_SCOPING_SEPARATOR)[-1]
        institution = Institution.objects.filter(
            domains__contains=[str(seps)]).first()
        if not institution:
            return redirect('auth:login')

        if not eppn:
            message = 'login failed: permission denied.'
            logging.info(message)
            messages.error(self.request, message)
            return redirect('auth:login')
        eppn_user = get_user(eppn=eppn)
        if eppn_user:
            try:
                others = eppn_user.affiliated_institutions.exclude(
                    id=institution.id).get()
            except Institution.DoesNotExist:
                pass
            else:
                eppn_user.affiliated_institutions.remove(others)

            eppn_user.affiliated_institutions.add(institution)
            if 'GakuninRDMAdmin' in request.environ['HTTP_AUTH_ENTITLEMENT']:
                # login success
                # code is below this if/else tree
                eppn_user.is_staff = True
                eppn_user.save()
            else:
                # login failure occurs and the screen transits to the error screen
                # not sure about this code
                eppn_user.is_staff = False
                eppn_user.save()
                message = 'login failed: permission denied.'
                logging.info(message)
                messages.error(self.request, message)
                return redirect('auth:login')
        else:
            if 'GakuninRDMAdmin' not in request.environ[
                    'HTTP_AUTH_ENTITLEMENT']:
                message = 'login failed: no user with matching eppn'
                messages.error(self.request, message)
                return redirect('auth:login')
            else:
                new_user, created = get_or_create_user(
                    request.environ['HTTP_AUTH_DISPLAYNAME'] or 'NO NAME',
                    eppn,
                    reset_password=False)
                USE_EPPN = login_by_eppn()
                if USE_EPPN:
                    new_user.eppn = eppn
                    new_user.have_email = False
                else:
                    new_user.eppn = None
                    new_user.have_email = True
                new_user.is_staff = True
                new_user.eppn = eppn
                new_user.have_email = False
                new_user.save()
                new_user.affiliated_institutions.add(institution)
                eppn_user = new_user

        guid = Guid.objects.get(
            object_id=eppn_user.id,
            content_type_id=ContentType.objects.get_for_model(OSFUser).id)
        if not userkey_generation_check(guid._id):
            userkey_generation(guid._id)
        login(request,
              eppn_user,
              backend='api.base.authentication.backends.ODMBackend')

        # Transit to the administrator's home screen
        return redirect(self.get_success_url())
Esempio n. 17
0
 def test_get_or_create_user_with_blacklisted_domain(self):
     fullname = 'Kanye West'
     username = '******'
     with assert_raises(ValidationError) as e:
         get_or_create_user(fullname, username, True)
     assert_equal(e.exception.message, 'Invalid Email')
Esempio n. 18
0
    def authenticate(self, request):
        """
        Handle CAS institution authentication request.

        The JWT `data` payload is expected in the following structure:
        {
            "provider": {
                "idp":  "",
                "id":   "",
                "user": {
                    "username":     "",
                    "fullname":     "",
                    "familyName":   "",
                    "givenName":    "",
                    "middleNames":  "",
                    "suffix":       "",
                }
            }
        }

        :param request: the POST request
        :return: user, None if authentication succeed
        :raises: AuthenticationFailed if authentication fails
        """

        # Verify / decrypt / decode the payload
        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256',
            )
        except (jwt.InvalidTokenError, TypeError, jwe.exceptions.MalformedData):
            raise AuthenticationFailed

        # Load institution and user data
        data = json.loads(payload['data'])
        provider = data['provider']
        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed('Invalid institution id: "{}"'.format(provider['id']))
        username = provider['user'].get('username')
        fullname = provider['user'].get('fullname')
        given_name = provider['user'].get('givenName')
        family_name = provider['user'].get('familyName')
        middle_names = provider['user'].get('middleNames')
        suffix = provider['user'].get('suffix')
        department = provider['user'].get('department')

        # Use given name and family name to build full name if it is not provided
        if given_name and family_name and not fullname:
            fullname = given_name + ' ' + family_name

        # Non-empty full name is required. Fail the auth and inform sentry if not provided.
        if not fullname:
            message = 'Institution login failed: fullname required for ' \
                      'user "{}" from institution "{}"'.format(username, provider['id'])
            sentry.log_message(message)
            raise AuthenticationFailed(message)

        # Get an existing user or create a new one. If a new user is created, the user object is
        # confirmed but not registered,which is temporarily of an inactive status. If an existing
        # user is found, it is also possible that the user is inactive (e.g. unclaimed, disabled,
        # unconfirmed, etc.).
        user, created = get_or_create_user(fullname, username, reset_password=False)

        # Existing but inactive users need to be either "activated" or failed the auth
        activation_required = False
        new_password_required = False
        if not created:
            try:
                drf.check_user(user)
                logger.info('Institution SSO: active user "{}"'.format(username))
            except exceptions.UnclaimedAccountError:
                # Unclaimed user (i.e. a user that has been added as an unregistered contributor)
                user.unclaimed_records = {}
                activation_required = True
                # Unclaimed users have an unusable password when being added as an unregistered
                # contributor. Thus a random usable password must be assigned during activation.
                new_password_required = True
                logger.info('Institution SSO: unclaimed contributor "{}"'.format(username))
            except exceptions.UnconfirmedAccountError:
                if user.has_usable_password():
                    # Unconfirmed user from default username / password signup
                    user.email_verifications = {}
                    activation_required = True
                    # Unconfirmed users already have a usable password set by the creator during
                    # sign-up. However, it must be overwritten by a new random one so the creator
                    # (if he is not the real person) can not access the account after activation.
                    new_password_required = True
                    logger.info('Institution SSO: unconfirmed user "{}"'.format(username))
                else:
                    # Login take-over has not been implemented for unconfirmed user created via
                    # external IdP login (ORCiD).
                    message = 'Institution SSO is not eligible for an unconfirmed account ' \
                              'created via external IdP login: username = "******"'.format(username)
                    sentry.log_message(message)
                    logger.error(message)
                    return None, None
            except exceptions.DeactivatedAccountError:
                # Deactivated user: login is not allowed for deactivated users
                message = 'Institution SSO is not eligible for a deactivated account: ' \
                          'username = "******"'.format(username)
                sentry.log_message(message)
                logger.error(message)
                return None, None
            except exceptions.MergedAccountError:
                # Merged user: this shouldn't happen since merged users do not have an email
                message = 'Institution SSO is not eligible for a merged account: ' \
                          'username = "******"'.format(username)
                sentry.log_message(message)
                logger.error(message)
                return None, None
            except exceptions.InvalidAccountError:
                # Other invalid status: this shouldn't happen unless the user happens to be in a
                # temporary state. Such state requires more updates before the user can be saved
                # to the database. (e.g. `get_or_create_user()` creates a temporary-state user.)
                message = 'Institution SSO is not eligible for an inactive account with ' \
                          'an unknown or invalid status: username = "******"'.format(username)
                sentry.log_message(message)
                logger.error(message)
                return None, None
        else:
            logger.info('Institution SSO: new user "{}"'.format(username))

        # The `department` field is updated each login when it was changed.
        if department and user.department != department:
            user.department = department
            user.save()

        # Both created and activated accounts need to be updated and registered
        if created or activation_required:

            if given_name:
                user.given_name = given_name
            if family_name:
                user.family_name = family_name
            if middle_names:
                user.middle_names = middle_names
            if suffix:
                user.suffix = suffix

            # Users claimed or confirmed via institution SSO should have their full name updated
            if activation_required:
                user.fullname = fullname

            user.update_date_last_login()

            # Relying on front-end validation until `accepted_tos` is added to the JWT payload
            user.accepted_terms_of_service = timezone.now()

            # Register and save user
            password = str(uuid.uuid4()) if new_password_required else None
            user.register(username, password=password)
            user.save()

            # Send confirmation email for all three: created, confirmed and claimed
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                mimetype='html',
                user=user,
                domain=DOMAIN,
                osf_support_email=OSF_SUPPORT_EMAIL,
                storage_flag_is_active=waffle.flag_is_active(request, features.STORAGE_I18N),
            )

        # Affiliate the user if not previously affiliated
        if not user.is_affiliated_with_institution(institution):
            user.affiliated_institutions.add(institution)
            user.save()

        return user, None
Esempio n. 19
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(
            message.sender_email,
            CONFERENCE_FAILED,
            fullname=message.sender_display,
            can_change_preferences=False,
            logo=settings.OSF_MEETINGS_LOGO
        )

    with transaction.atomic():
        user, user_created = get_or_create_user(
            message.sender_display,
            message.sender_email,
            is_spam=message.is_spam,
        )
        if user_created:
            if utils.is_valid_email(user.fullname):
                user.fullname = user._id  # Users cannot use an email as their full name

            user.save()  # need to save in order to access m2m fields (e.g. tags)
            user.add_system_tag('osf4m')
            user.update_date_last_login()
            user.save()

            # must save the user first before accessing user._id
            set_password_url = web_url_for(
                'reset_password_get',
                uid=user._id,
                token=user.verification_key_v2['token'],
                _absolute=True,
            )
        else:
            set_password_url = None

        # Always create a new meeting node
        node = Node.objects.create(
            title=message.subject,
            creator=user
        )
        node.add_system_tag('osf4m')
        node.save()

        utils.provision_node(conference, message, node, user)
        created_user = user if user_created else None
        utils.record_message(message, node, created_user)
    # Prevent circular import error
    from framework.auth import signals as auth_signals
    if user_created:
        auth_signals.user_confirmed.send(user)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        'addon_view_or_download_file',
        path=message.attachments[0].filename,
        provider='osfstorage',
        action='download',
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for(
            'conference_results',
            meeting=message.conference_name,
            _absolute=True,
        ),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
        can_change_preferences=False,
        logo=settings.OSF_MEETINGS_LOGO
    )
    if user_created:
        signals.osf4m_user_created.send(user, conference=conference, node=node)
 def test_get_or_create_user_with_blacklisted_domain(self):
     fullname = 'Kanye West'
     username = '******'
     with assert_raises(BlacklistedEmailError) as e:
         get_or_create_user(fullname, username, is_spam=True)
     assert_equal(e.exception.message, 'Invalid Email')
Esempio n. 21
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(
            message.sender_email,
            CONFERENCE_FAILED,
            fullname=message.sender_display,
        )

    created = []

    with TokuTransaction():
        user, user_created = get_or_create_user(
            message.sender_display,
            message.sender_email,
            message.is_spam,
        )
        if user_created:
            created.append(user)
            user.system_tags.append('osf4m')
            set_password_url = web_url_for(
                'reset_password_get',
                verification_key=user.verification_key,
                _absolute=True,
            )
            user.date_last_login = datetime.utcnow()
            user.save()
        else:
            set_password_url = None

        node, node_created = utils.get_or_create_node(message.subject, user)
        if node_created:
            created.append(node)
            node.system_tags.append('osf4m')
            node.save()

        utils.provision_node(conference, message, node, user)
        utils.record_message(message, created)
    # Prevent circular import error
    from framework.auth import signals as auth_signals
    if user_created:
        auth_signals.user_confirmed.send(user)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        'addon_view_or_download_file',
        path=message.attachments[0].filename,
        provider='osfstorage',
        action='download',
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for(
            'conference_results',
            meeting=message.conference_name,
            _absolute=True,
        ),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
    )
    if node_created and user_created:
        signals.osf4m_user_created.send(user, conference=conference, node=node)
Esempio n. 22
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(
            message.sender_email,
            CONFERENCE_FAILED,
            fullname=message.sender_display,
        )

    created = []

    with TokuTransaction():
        user, user_created = get_or_create_user(
            message.sender_display,
            message.sender_email,
            message.is_spam,
        )
        if user_created:
            created.append(user)
            user.system_tags.append('osf4m')
            set_password_url = web_url_for(
                'reset_password',
                verification_key=user.verification_key,
                _absolute=True,
            )
            user.date_last_login = datetime.utcnow()
            user.save()
        else:
            set_password_url = None

        node, node_created = utils.get_or_create_node(message.subject, user)
        if node_created:
            created.append(node)
            node.system_tags.append('osf4m')
            node.save()

        utils.provision_node(conference, message, node, user)
        utils.record_message(message, created)
    # Prevent circular import error
    from framework.auth import signals as auth_signals
    if user_created:
        auth_signals.user_confirmed.send(user)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        'addon_view_or_download_file',
        path=message.attachments[0].filename,
        provider='osfstorage',
        action='download',
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for(
            'conference_results',
            meeting=message.conference_name,
            _absolute=True,
        ),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
    )
    if node_created and user_created:
        signals.osf4m_user_created.send(user, conference=conference, node=node)
    def authenticate(self, request):
        """
        Handle CAS institution authentication request.

        The JWT `data` payload is expected in the following structure:
        {
            "provider": {
                "idp":  "",
                "id":   "",
                "user": {
                    "username":     "",
                    "fullname":     "",
                    "familyName":   "",
                    "givenName":    "",
                    "middleNames":  "",
                    "suffix":       "",
                }
            }
        }

        :param request: the POST request
        :return: user, None if authentication succeed
        :raises: AuthenticationFailed if authentication fails
        """

        try:
            payload = jwt.decode(
                jwe.decrypt(request.body, settings.JWE_SECRET),
                settings.JWT_SECRET,
                options={'verify_exp': False},
                algorithm='HS256',
            )
        except (jwt.InvalidTokenError, TypeError):
            raise AuthenticationFailed

        data = json.loads(payload['data'])
        provider = data['provider']

        institution = Institution.load(provider['id'])
        if not institution:
            raise AuthenticationFailed('Invalid institution id specified "{}"'.format(provider['id']))

        username = provider['user'].get('username')
        fullname = provider['user'].get('fullname')
        given_name = provider['user'].get('givenName')
        family_name = provider['user'].get('familyName')
        middle_names = provider['user'].get('middleNames')
        suffix = provider['user'].get('suffix')

        # use given name and family name to build full name if not provided
        if given_name and family_name and not fullname:
            fullname = given_name + ' ' + family_name

        # institution must provide `fullname`, otherwise we fail the authentication and inform sentry
        if not fullname:
            message = 'Institution login failed: fullname required' \
                      ' for user {} from institution {}'.format(username, provider['id'])
            sentry.log_message(message)
            raise AuthenticationFailed(message)

        # `get_or_create_user()` guesses names from fullname
        # replace the guessed ones if the names are provided from the authentication
        user, created = get_or_create_user(fullname, username, reset_password=False)
        if created:
            if given_name:
                user.given_name = given_name
            if family_name:
                user.family_name = family_name
            if middle_names:
                user.middle_names = middle_names
            if suffix:
                user.suffix = suffix
            user.update_date_last_login()

            # Relying on front-end validation until `accepted_tos` is added to the JWT payload
            user.accepted_terms_of_service = timezone.now()

            # save and register user
            user.save()
            user.register(username)

            # send confirmation email
            send_mail(
                to_addr=user.username,
                mail=WELCOME_OSF4I,
                mimetype='html',
                user=user,
                domain=DOMAIN,
                osf_support_email=OSF_SUPPORT_EMAIL,
                storage_flag_is_active=waffle.flag_is_active(request, features.STORAGE_I18N),
            )

        if not user.is_affiliated_with_institution(institution):
            user.affiliated_institutions.add(institution)
            user.save()

        return user, None
Esempio n. 24
0
def add_poster_by_email(conference, message):
    """
    :param Conference conference:
    :param ConferenceMessage message:
    """
    # Fail if no attachments
    if not message.attachments:
        return send_mail(
            message.sender_email,
            CONFERENCE_FAILED,
            fullname=message.sender_display,
        )

    nodes_created = []
    users_created = []

    with transaction.atomic():
        user, user_created = get_or_create_user(
            message.sender_display,
            message.sender_email,
            is_spam=message.is_spam,
        )
        if user_created:
            user.save()  # need to save in order to access m2m fields (e.g. tags)
            users_created.append(user)
            user.add_system_tag('osf4m')
            user.update_date_last_login()
            user.save()

            # must save the user first before accessing user._id
            set_password_url = web_url_for(
                'reset_password_get',
                uid=user._id,
                token=user.verification_key_v2['token'],
                _absolute=True,
            )
        else:
            set_password_url = None

        node, node_created = Node.objects.get_or_create(
            title__iexact=message.subject,
            is_deleted=False,
            _contributors__guids___id=user._id,
            defaults={
                'title': message.subject,
                'creator': user
            }
        )
        if node_created:
            nodes_created.append(node)
            node.add_system_tag('osf4m')
            node.save()

        utils.provision_node(conference, message, node, user)
        utils.record_message(message, nodes_created, users_created)
    # Prevent circular import error
    from framework.auth import signals as auth_signals
    if user_created:
        auth_signals.user_confirmed.send(user)

    utils.upload_attachments(user, node, message.attachments)

    download_url = node.web_url_for(
        'addon_view_or_download_file',
        path=message.attachments[0].filename,
        provider='osfstorage',
        action='download',
        _absolute=True,
    )

    # Send confirmation email
    send_mail(
        message.sender_email,
        CONFERENCE_SUBMITTED,
        conf_full_name=conference.name,
        conf_view_url=web_url_for(
            'conference_results',
            meeting=message.conference_name,
            _absolute=True,
        ),
        fullname=message.sender_display,
        user_created=user_created,
        set_password_url=set_password_url,
        profile_url=user.absolute_url,
        node_url=node.absolute_url,
        file_url=download_url,
        presentation_type=message.conference_category.lower(),
        is_spam=message.is_spam,
    )
    if node_created and user_created:
        signals.osf4m_user_created.send(user, conference=conference, node=node)