Пример #1
0
def logout_view(request):
    """SAML Logout Request initiator

    This view initiates the SAML2 Logout request
    using the pysaml2 library to create the LogoutRequest.
    """
    log.debug('Logout process started')
    state = StateCache(request.session)

    client = Saml2Client(request.saml2_config, state_cache=state,
                         identity_cache=IdentityCache(request.session))
    subject_id = _get_name_id(request.session)
    if subject_id is None:
        log.warning(
            'The session does not contains the subject id for user ')
        location = request.registry.settings.get('saml2.logout_redirect_url')

    else:
        logouts = client.global_logout(subject_id)
        loresponse = logouts.values()[0]
        # loresponse is a dict for REDIRECT binding, and LogoutResponse for SOAP binding
        if isinstance(loresponse, LogoutResponse):
            if loresponse.status_ok():
                log.debug('Performing local logout of {!r}'.format(authenticated_userid(request)))
                headers = logout(request)
                location = request.registry.settings.get('saml2.logout_redirect_url')
                return HTTPFound(location=location, headers=headers)
            else:
                return HTTPInternalServerError('Logout failed')
        headers_tuple = loresponse[1]['headers']
        location = headers_tuple[0][1]

    state.sync()
    log.debug('Redirecting to {!r} to continue the logout process'.format(location))
    return HTTPFound(location=location)
Пример #2
0
def logout_service(request):
    """SAML Logout Response endpoint

    The IdP will send the logout response to this view,
    which will process it with pysaml2 help and log the user
    out.
    Note that the IdP can request a logout even when
    we didn't initiate the process as a single logout
    request started by another SP.
    """
    log.debug('Logout service started')

    state = StateCache(request.session)
    client = Saml2Client(request.saml2_config, state_cache=state,
                         identity_cache=IdentityCache(request.session))
    settings = request.registry.settings

    logout_redirect_url = settings.get('saml2.logout_redirect_url')
    next_page = request.GET.get('next_page', logout_redirect_url)

    if 'SAMLResponse' in request.GET:  # we started the logout
        log.debug('Receiving a logout response from the IdP')
        response = client.parse_logout_request_response(
            request.GET['SAMLResponse'],
            BINDING_HTTP_REDIRECT
        )
        state.sync()
        if response and response.status_ok():
            headers = logout(request)
            return HTTPFound(next_page, headers=headers)
        else:
            log.error('Unknown error during the logout')
            return HTTPBadRequest('Error during logout')

    elif 'SAMLRequest' in request.GET:  # logout started by the IdP
        log.debug('Receiving a logout request from the IdP')
        subject_id = _get_name_id(request.session)
        if subject_id is None:
            log.warning(
                'The session does not contain the subject id for user {0} '
                'Performing local logout'.format(
                    authenticated_userid(request)
                )
            )
            headers = logout(request)
            return HTTPFound(location=next_page, headers=headers)
        else:
            http_info = client.handle_logout_request(
                request.GET['SAMLRequest'],
                subject_id,
                BINDING_HTTP_REDIRECT,
                relay_state=request.GET['RelayState']
            )
            state.sync()
            location = get_location(http_info)
            headers = logout(request)
            return HTTPFound(location=location, headers=headers)
    else:
        log.error('No SAMLResponse or SAMLRequest parameter found')
        raise HTTPNotFound('No SAMLResponse or SAMLRequest parameter found')
Пример #3
0
    def resend_code_action(self, index, post_data):
        self.user = get_session_user(self.request)
        data = self.user.to_dict().get(self.data_attribute, [])

        # Catch the unlikely event when the user have e.g. removed all entries
        # in a separate tab, or one in the middle and then tries to resend the
        # code for a non-existing entry.
        # This is an incomplete fix since it is better if we can get the list
        # from the UI and then check that the entry which the client want to
        # resend the code for corresponds to the same entry we get from
        # data[index].
        try:
            data_to_resend = data[index]
        except IndexError:
            log.warning('Index error in resend_code_action, user {!s}'.format(self.user))
            message = self.verify_messages['out_of_sync']
            return {
                'result': 'out_of_sync',
                'message': get_localizer(self.request).translate(message),
            }

        data_id = self.get_verification_data_id(data_to_resend)
        reference, code = new_verification_code(
            self.request, self.data_attribute, data_id,
            self.user, hasher=get_short_hash,
        )
        self.send_verification_code(data_id, reference, code)
        msg = self.verify_messages['new_code_sent']
        return {
            'result': 'success',
            'message': msg,
        }
Пример #4
0
def check_password(vccs_url, password, user, vccs=None):
    """ Try to validate a user provided password.

    Returns False or a Password instance with data about the credential that validated.

    :param vccs_url: URL to VCCS authentication backend
    :param password: plaintext password
    :param user: User object
    :param vccs: optional vccs client instance

    :type vccs_url: string
    :type password: string
    :type user: DashboardUser or DashboardLegacyUser or User
    :type vccs: None or VCCSClient
    :rtype: False or Password
    """
    # upgrade DashboardLegacyUser to DashboardUser
    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)

    for cred in user.credentials.filter(Password).to_list():
        if vccs is None:
            vccs = get_vccs_client(vccs_url)
        factor = vccs_client.VCCSPasswordFactor(
            password,
            credential_id=str(cred.credential_id),
            salt=cred.salt,
            )
        try:
            if vccs.authenticate(str(user.user_id), [factor]):
                return cred
        except Exception as exc:
            log.warning("VCCS authentication threw exception: {!s}".format(exc))
            pass
    return False
Пример #5
0
    def verify_action(self, index, post_data):
        """ Common action to verify some given data.
            You can override in subclasses
        """
        self.user = get_session_user(self.request)

        # Catch the unlikely event when the user have e.g. removed all entries
        # in a separate tab, or one in the middle and then tries to resend the
        # code for a non-existing entry.
        # This is an incomplete fix since it is better if we can get the list
        # from the UI and then check that the entry which the client want to
        # resend the code for corresponds to the same entry we get from
        # data[index].
        try:
            _data = self.user.to_dict().get(self.data_attribute, [])
            data_to_verify = _data[index]
        except IndexError:
            log.warning('Index error in verify_action, user {!s}'.format(self.user))
            message = self.verify_messages['out_of_sync']
            return {
                'result': 'out_of_sync',
                'message': get_localizer(self.request).translate(message),
            }

        data_id = self.get_verification_data_id(data_to_verify)
        return self._verify_action(data_id, post_data)
Пример #6
0
    def verify_action(self, index, post_data):
        """ Common action to verify some given data.
            You can override in subclasses
        """
        self.user = get_session_user(self.request)

        # Catch the unlikely event when the user have e.g. removed all entries
        # in a separate tab, or one in the middle and then tries to resend the
        # code for a non-existing entry.
        # This is an incomplete fix since it is better if we can get the list
        # from the UI and then check that the entry which the client want to
        # resend the code for corresponds to the same entry we get from
        # data[index].
        try:
            _data = self.user.to_dict().get(self.data_attribute, [])
            data_to_verify = _data[index]
        except IndexError:
            log.warning('Index error in verify_action, user {!s}'.format(
                self.user))
            message = self.verify_messages['out_of_sync']
            return {
                'result': 'out_of_sync',
                'message': get_localizer(self.request).translate(message),
            }

        data_id = self.get_verification_data_id(data_to_verify)
        return self._verify_action(data_id, post_data)
Пример #7
0
 def sync_user(self):
     log.warning('User {!s} could not be saved (views/__init__.py)'.format(self.user))
     self.user = sync_user(self.request, self.context, self.user)
     message = self.verify_messages['out_of_sync']
     return {
         'result': 'out_of_sync',
         'message': get_localizer(self.request).translate(message),
     }
Пример #8
0
 def sync_user(self):
     log.warning('User {!s} could not be saved (views/__init__.py)'.format(
         self.user))
     self.user = sync_user(self.request, self.context, self.user)
     message = self.verify_messages['out_of_sync']
     return {
         'result': 'out_of_sync',
         'message': get_localizer(self.request).translate(message),
     }
Пример #9
0
def check_password(vccs_url, password, user, vccs=None):
    """ Try to validate a user provided password.

    Returns False or a Password instance with data about the credential that validated.

    :param vccs_url: URL to VCCS authentication backend
    :param password: plaintext password
    :param user: User object
    :param vccs: optional vccs client instance

    :type vccs_url: string
    :type password: string
    :type user: DashboardUser or DashboardLegacyUser or User
    :type vccs: None or VCCSClient
    :rtype: False or Password
    """
    # upgrade DashboardLegacyUser to DashboardUser
    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)

    for cred in user.credentials.filter(Password).to_list():
        if vccs is None:
            vccs = get_vccs_client(vccs_url)
        factor = vccs_client.VCCSPasswordFactor(
            password,
            credential_id=str(cred.credential_id),
            salt=cred.salt,
        )
        try:
            if vccs.authenticate(str(user.user_id), [factor]):
                return cred
        except Exception as exc:
            log.warning(
                "VCCS authentication threw exception: {!s}".format(exc))
            pass
    return False
Пример #10
0
    def resend_code_action(self, index, post_data):
        self.user = get_session_user(self.request)
        data = self.user.to_dict().get(self.data_attribute, [])

        # Catch the unlikely event when the user have e.g. removed all entries
        # in a separate tab, or one in the middle and then tries to resend the
        # code for a non-existing entry.
        # This is an incomplete fix since it is better if we can get the list
        # from the UI and then check that the entry which the client want to
        # resend the code for corresponds to the same entry we get from
        # data[index].
        try:
            data_to_resend = data[index]
        except IndexError:
            log.warning('Index error in resend_code_action, user {!s}'.format(
                self.user))
            message = self.verify_messages['out_of_sync']
            return {
                'result': 'out_of_sync',
                'message': get_localizer(self.request).translate(message),
            }

        data_id = self.get_verification_data_id(data_to_resend)
        reference, code = new_verification_code(
            self.request,
            self.data_attribute,
            data_id,
            self.user,
            hasher=get_short_hash,
        )
        self.send_verification_code(data_id, reference, code)
        msg = self.verify_messages['new_code_sent']
        return {
            'result': 'success',
            'message': msg,
        }
Пример #11
0
def add_credentials(vccs_url, old_password, new_password, user):
    """
    Add a new password to a user. Revokes the old one, if one is given.

    Returns True on success.

    :param vccs_url: URL to VCCS authentication backend
    :param old_password: plaintext current password
    :param new_password: plaintext new password
    :param user: user object

    :type vccs_url: string
    :type old_password: string
    :type user: OldUser
    :rtype: bool
    """
    password_id = ObjectId()
    vccs = get_vccs_client(vccs_url)
    new_factor = vccs_client.VCCSPasswordFactor(new_password,
                                                credential_id=str(password_id))

    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)

    old_factor = None
    checked_password = None
    # remember if an old password was supplied or not, without keeping it in
    # memory longer than we have to
    old_password_supplied = bool(old_password)
    if user.passwords.count > 0 and old_password:
        # Find the old credential to revoke
        checked_password = check_password(vccs_url,
                                          old_password,
                                          user,
                                          vccs=vccs)
        del old_password  # don't need it anymore, try to forget it
        if not checked_password:
            return False
        old_factor = vccs_client.VCCSRevokeFactor(
            str(checked_password.credential_id),
            'changing password',
            reference='dashboard',
        )

    if not vccs.add_credentials(str(user.user_id), [new_factor]):
        log.warning(
            "Failed adding password credential {!r} for user {!r}".format(
                new_factor.credential_id, user))
        return False  # something failed
    log.debug("Added password credential {!s} for user {!s}".format(
        new_factor.credential_id, user))

    if old_factor:
        vccs.revoke_credentials(str(user.user_id), [old_factor])
        user.passwords.remove(checked_password.key)
        log.debug("Revoked old credential {!s} (user {!s})".format(
            old_factor.credential_id, user))

    if not old_password_supplied:
        # TODO: Revoke all current credentials on password reset for now
        revoked = []
        for password in user.credentials.filter(Password).to_list():
            revoked.append(
                vccs_client.VCCSRevokeFactor(str(password.credential_id),
                                             'reset password',
                                             reference='dashboard'))
            log.debug(
                "Revoking old credential (password reset) {!s} (user {!r})".
                format(password.credential_id, user))
            user.passwords.remove(password.key)
        if revoked:
            try:
                vccs.revoke_credentials(str(user.user_id), revoked)
            except vccs_client.VCCSClientHTTPError:
                # Password already revoked
                # TODO: vccs backend should be changed to return something more informative than
                # TODO: VCCSClientHTTPError when the credential is already revoked or just return success.
                log.warning(
                    "VCCS failed to revoke all passwords for user {!s}".format(
                        user))
                pass

    new_password = Password(
        credential_id=password_id,
        salt=new_factor.salt,
        application='dashboard',
    )
    user.passwords.add(new_password)

    return True
Пример #12
0
def add_credentials(vccs_url, old_password, new_password, user):
    """
    Add a new password to a user. Revokes the old one, if one is given.

    Returns True on success.

    :param vccs_url: URL to VCCS authentication backend
    :param old_password: plaintext current password
    :param new_password: plaintext new password
    :param user: user object

    :type vccs_url: string
    :type old_password: string
    :type user: User
    :rtype: bool
    """
    password_id = ObjectId()
    vccs = get_vccs_client(vccs_url)
    new_factor = vccs_client.VCCSPasswordFactor(new_password,
                                                credential_id=str(password_id))

    passwords = user.get_passwords()
    old_factor = None
    if passwords and old_password:
        # Find the old credential to revoke
        old_password = check_password(vccs_url, old_password, user, vccs=vccs)
        if not old_password:
            return False
        old_factor = vccs_client.VCCSRevokeFactor(
            str(old_password['id']),
            'changing password',
            reference='dashboard',
        )

    if not vccs.add_credentials(str(user.get_id()), [new_factor]):
        log.warning("Failed adding password credential {!r} for user {!r}".format(
            new_factor.credential_id, user.get_id()))
        return False  # something failed
    log.debug("Added password credential {!s} for user {!s}".format(
        new_factor.credential_id, user.get_id()))

    if old_factor:
        # Use the user_id_hint inserted by check_password() until we know all
        # credentials use str(user['_id']) as user_id.
        vccs.revoke_credentials(old_password['user_id_hint'], [old_factor])
        passwords.remove(old_password)
        log.debug("Revoked old credential {!s} (user {!s})".format(
            old_factor.credential_id, user.get_id()))
    elif not old_password:
        # TODO: Revoke all current credentials on password reset for now
        revoked = []
        for password in passwords:
            revoked.append(vccs_client.VCCSRevokeFactor(str(password['id']), 'reset password', reference='dashboard'))
            log.debug("Revoked old credential (password reset) {!s} (user {!s})".format(
                password['id'], user.get_id()))
        if revoked:
            vccs.revoke_credentials(str(user.get_id()), revoked)
        del passwords[:]

    passwords.append({
        'id': password_id,
        'salt': new_factor.salt,
        'source': 'dashboard',
        'created_ts': datetime.now(),
    })
    user.set_passwords(passwords)

    return True
Пример #13
0
def add_credentials(vccs_url, old_password, new_password, user):
    """
    Add a new password to a user. Revokes the old one, if one is given.

    Returns True on success.

    :param vccs_url: URL to VCCS authentication backend
    :param old_password: plaintext current password
    :param new_password: plaintext new password
    :param user: user object

    :type vccs_url: string
    :type old_password: string
    :type user: OldUser
    :rtype: bool
    """
    password_id = ObjectId()
    vccs = get_vccs_client(vccs_url)
    new_factor = vccs_client.VCCSPasswordFactor(new_password,
                                                credential_id=str(password_id))

    if isinstance(user, DashboardLegacyUser):
        user = DashboardUser(data=user._mongo_doc)

    old_factor = None
    checked_password = None
    # remember if an old password was supplied or not, without keeping it in
    # memory longer than we have to
    old_password_supplied = bool(old_password)
    if user.passwords.count > 0 and old_password:
        # Find the old credential to revoke
        checked_password = check_password(vccs_url, old_password, user, vccs=vccs)
        del old_password  # don't need it anymore, try to forget it
        if not checked_password:
            return False
        old_factor = vccs_client.VCCSRevokeFactor(
            str(checked_password.credential_id),
            'changing password',
            reference='dashboard',
        )

    if not vccs.add_credentials(str(user.user_id), [new_factor]):
        log.warning("Failed adding password credential {!r} for user {!r}".format(
            new_factor.credential_id, user))
        return False  # something failed
    log.debug("Added password credential {!s} for user {!s}".format(
        new_factor.credential_id, user))

    if old_factor:
        vccs.revoke_credentials(str(user.user_id), [old_factor])
        user.passwords.remove(checked_password.key)
        log.debug("Revoked old credential {!s} (user {!s})".format(
            old_factor.credential_id, user))

    if not old_password_supplied:
        # TODO: Revoke all current credentials on password reset for now
        revoked = []
        for password in user.credentials.filter(Password).to_list():
            revoked.append(vccs_client.VCCSRevokeFactor(str(password.credential_id), 'reset password', reference='dashboard'))
            log.debug("Revoking old credential (password reset) {!s} (user {!r})".format(
                password.credential_id, user))
            user.passwords.remove(password.key)
        if revoked:
            try:
                vccs.revoke_credentials(str(user.user_id), revoked)
            except vccs_client.VCCSClientHTTPError:
                # Password already revoked
                # TODO: vccs backend should be changed to return something more informative than
                # TODO: VCCSClientHTTPError when the credential is already revoked or just return success.
                log.warning("VCCS failed to revoke all passwords for user {!s}".format(user))
                pass

    new_password = Password(credential_id = password_id,
                            salt = new_factor.salt,
                            application = 'dashboard',
                            )
    user.passwords.add(new_password)

    return True