Example #1
0
def confirm_login(request):
    """Renders a login form for a user that needs to login in order to
    authenticate an active OpenID authentication request.
    """
    if 'form.submitted' in request.POST:
        if request.POST.get('csrf_token') != request.session.get_csrf_token():
            raise Forbidden

        login = request.POST.get('login', '')
        password = request.POST.get('password', '')
        user = DBSession().query(User).filter_by(username=login).first()

        if user is not None and user.check_password(password):
            headers = remember(request, user.username)
            return HTTPFound(
                location=route_url('openid_confirm_login_success', request),
                headers=headers)
        else:
            request.session.flash(_(u'Failed to log in, please try again.'))

    options = {
        'title': _(u'Log in to authenticate OpenID request'),
        'login_name': request.params.get('login', ''),
        'action_url': route_url('openid_confirm_login', request),
        #'reset_url': route_url('reset_password', request),
        'csrf_token': request.session.get_csrf_token(),
    }
    return render_to_response('templates/openid_confirm_login.pt', options, request)
    def test_change_password(self, remember):
        from webidentity.models import PasswordReset
        from webidentity.models import User

        remember.return_value = [('X-Login', 'john.doe')]
        user = User(u'john.doe', u'secret', u'*****@*****.**')
        reset = PasswordReset(1, datetime.now() + timedelta(days=7), u'uniquetoken')

        session = DBSession()
        session.add(reset)
        session.add(user)
        session.flush()

        self.assertEquals(1, user.id)
        self.assertEquals(1, session.query(PasswordReset)\
            .filter(PasswordReset.user_id == 1)\
            .filter(PasswordReset.token == u'uniquetoken')\
            .filter(PasswordReset.expires >= datetime.now())\
            .count())

        view = self._makeView(post={
            'token': 'uniquetoken',
            'password': '******',
            'confirm_password': '******'})
        response = view.change_password()
        self.assertEquals(dict(response.headers), {
            'Content-Length': '0',
            'X-Login': '******',
            'Content-Type': 'text/html; charset=UTF-8',
            'Location': 'http://example.com'})
        # Check that the password was changed
        self.failUnless(session.query(User).get(1).check_password('abc123'))
        # Check that the reset request was deleted
        self.assertEquals(0, session.query(PasswordReset).count())
    def test_login__form_submission__success_with_identity_wo_scheme(self, remember):
        from webidentity.views.login import login
        from webidentity.models import User
        session = DBSession()
        session.add(User(u'john.doe', u'secret', u'*****@*****.**'))
        self.assertEquals(
            session.query(User).filter_by(username=u'john.doe').first().email,
            u'*****@*****.**')

        remember.return_value = [('X-Login', 'john.doe')]
        request = testing.DummyRequest(environ={
            'wsgi.url_scheme': 'http',
        })
        token = request.session.new_csrf_token()
        request.POST = {
            'form.submitted': u'1',
            'login': u'example.com/id/john.doe',
            'password': u'secret',
            'csrf_token': token,
        }

        response = login(request)
        self.assertEquals(dict(response.headers), {
            'Content-Length': '0',
            'Content-Type': 'text/html; charset=UTF-8',
            'Location': 'http://example.com',
            'X-Login': u'john.doe'})
        self.assertEquals(request.session.pop_flash(), [u'You have successfully logged in.'])
Example #4
0
def yadis(request):
    """Serves the YADIS XRDS documents to facilitate service discovery."""
    OPENID_AX = 'http://openid.net/srv/ax/1.0'

    if request.matchdict.get('local_id', '').strip():
        session = DBSession()
        account = session.query(User).filter(User.username == request.matchdict['local_id']).first()
        if account is None:
            raise NotFound()

        # User specific XRDS document
        options = {
            'service_types': [discover.OPENID_2_0_TYPE, OPENID_AX, sreg.ns_uri],
            'local_id': identity_url(request, request.matchdict['local_id']),
            'uri': route_url('openid_endpoint', request),
        }
    else:
        # Server XRDS document
        options = {
            'service_types': [discover.OPENID_IDP_2_0_TYPE, OPENID_AX, sreg.ns_uri],
            'uri': route_url('openid_endpoint', request),
        }

    response = render_to_response('templates/openid_yadis.pt', options, request)
    response.content_type = 'application/xrds+xml'

    return response
def verify_accounts():
    """Verifies the create accounts against the password file."""
    engine = engine_from_config(get_config(), 'sqlalchemy.')
    initialize_sql(engine, False)
    session = DBSession()

    if len(sys.argv) < 3:
        print "Usage: {0} <config> <data>".format(sys.argv[0])
        sys.exit(1)

    users = dict((user.username, user) for user in session.query(User).all())
    failed = 0

    print "Checking.."
    passwords = open(sys.argv[2]).readlines()
    for line in passwords:
        username, password = line.strip().split('|', 1)
        if username not in users:
            print "Unknown user:"******"Invalid password for user", username
            failed += 1

    print "Checked", len(passwords), "passwords against", len(users), "users."
    print "Failures", failed
    if failed == 0:
        print "All OK"
    else:
        print "Data inconsistency, please investigate!"
    def test_change_password__success(self):
        from webidentity.views.user import change_password

        self.config.testing_securitypolicy(userid=u"dokai")
        self.config.add_route("change_password", "/change-password")

        user = User("dokai", "secret", "*****@*****.**")
        session = DBSession()
        session.add(user)
        request = testing.DummyRequest(
            post={
                "form.submitted": "1",
                "current_password": "******",
                "password": "******",
                "confirm_password": "******",
            }
        )
        token = request.session.new_csrf_token()
        request.POST["csrf_token"] = token

        self.assertEquals(
            change_password(request), {"action_url": "http://example.com/change-password", "csrf_token": token}
        )
        # Check that the password was changed correctly.
        self.failUnless(user.check_password("new_password"))
    def test_login__form_submission__invalid_password(self):
        from webidentity.views.login import login
        from webidentity.models import User
        session = DBSession()
        session.add(User(u'john.doe', u'secret', u'*****@*****.**'))
        self.assertEquals(
            session.query(User).filter_by(username=u'john.doe').first().email,
            u'*****@*****.**')

        request = testing.DummyRequest(environ={
            'wsgi.url_scheme': 'http',
        })
        token = request.session.new_csrf_token()
        request.POST = {
            'form.submitted': u'1',
            'login': u'john.doe',
            'password': u'thisiswrong',
            'csrf_token': token,
        }

        options = login(request)
        self.assertEquals(options, {
            'title': u'Login',
            'reset_url': 'http://example.com/reset-password',
            'action_url': 'http://example.com/login',
            'login': u'john.doe',
            'csrf_token': token})
    def test_log_activity__anonymous(self):
        from webidentity.views.user import log_activity

        session = DBSession()
        self.assertEquals(0, session.query(Activity).count())

        request = testing.DummyRequest()
        log_activity(request, Activity.LOGIN)
        self.assertEquals(0, session.query(Activity).count())
    def test_prune_expired__empty(self):
        from webidentity.models import PasswordReset

        view = self._makeView()
        session = DBSession()

        self.assertEquals(0, session.query(PasswordReset).count())
        view.prune_expired()
        self.assertEquals(0, session.query(PasswordReset).count())
    def test_home_page__authenticated(self):
        from webidentity.views.user import home_page

        self.config.testing_securitypolicy(userid=u"dokai")
        session = DBSession()
        session.add(User("dokai", "secret", "*****@*****.**"))
        request = testing.DummyRequest()

        self.assertEquals(home_page(request), {})
    def test_visited_sites__empty(self):
        from webidentity.views.user import visited_sites

        self.config.testing_securitypolicy(userid=u"dokai")

        session = DBSession()
        session.add(User("dokai", "secret", "*****@*****.**"))

        request = testing.DummyRequest()
        self.assertEquals(visited_sites(request), {"action_url": "form_action", "sites": []})
    def test_authenticated_user__authenticated(self):
        from webidentity.views.user import authenticated_user

        self.config.testing_securitypolicy(userid=u"dokai")
        session = DBSession()

        user = User("dokai", "secret", "*****@*****.**")
        session.add(user)

        request = testing.DummyRequest()
        self.assertEquals(user, authenticated_user(request))
    def test_password_change_form__expired_token(self):
        from webidentity.models import PasswordReset

        view = self._makeView()
        reset = PasswordReset(1, datetime.now() - timedelta(days=7))
        session = DBSession()
        session.add(reset)

        self.assertEquals(1, session.query(PasswordReset).filter_by(token=reset.token).count())

        view.request.matchdict['token'] = reset.token
        self.assertRaises(NotFound, view.password_change_form)
Example #14
0
def confirm_login_success(request):
    session = DBSession()
    result = session.query(CheckIdRequest)\
                .filter_by(key=request.environ.get('repoze.browserid', ''))\
                .first()

    if result is not None:
        openid_request = result.request
        session.delete(result)
        return auth_decision(request, openid_request)

    raise NotFound('Unknown OpenID request')
def populate_accounts():
    """Populates the database with production data."""
    engine = engine_from_config(get_config(), 'sqlalchemy.')
    initialize_sql(engine, False)
    session = DBSession()

    if len(sys.argv) < 3:
        print "Usage: {0} <config> <data>".format(sys.argv[0])
        sys.exit(1)

    for line in open(sys.argv[2]).readlines():
        username, password = line.strip().split('|', 1)
        session.add(User(username.strip(), password.strip(), '*****@*****.**'))
        print username

    session.flush()
    transaction.commit()
    def test_change_password__invalid_user(self):
        from webidentity.models import PasswordReset

        reset = PasswordReset(1, datetime.now() + timedelta(days=7), u'uniquetoken')
        session = DBSession()
        session.add(reset)

        self.assertEquals(1, session.query(PasswordReset)\
            .filter(PasswordReset.token == u'uniquetoken')\
            .filter(PasswordReset.expires >= datetime.now())\
            .count())

        view = self._makeView(post={
            'token': 'uniquetoken',
            'password': '******',
            'confirm_password': '******'})
        self.assertRaises(NotFound, view.change_password)
Example #17
0
def get_ax_attributes(request):
    """Returns a dictionary of type_uri => value mappings containing the AX
    attributes for the currently authenticated user.
    """
    user = authenticated_user(request)
    if user is None:
        return {}

    session = DBSession()
    # TODO: Should we convert persona_id to an integer first?
    persona = session.query(Persona)\
                .join(User)\
                .filter(User.id == user.id)\
                .filter(Persona.id == request.params.get('persona', ''))\
                .first()
    if persona is None:
        return {}

    # Get the attribute values from the selected persona.
    return dict((a.type_uri, a.value) for a in persona.attributes)
    def test_send_confirmation_message(self, send_mail):
        from webidentity.models import PasswordReset
        from webidentity.models import User
        from email.message import Message

        self.config.add_settings(DUMMY_SETTINGS)
        session = DBSession()
        user = User(u'john.doe', u'secret', u'*****@*****.**')
        session.add(user)
        self.assertEquals(u'john.doe', session.query(User).get(1).username)

        view = self._makeView(post={'username': u'john.doe'})
        response = view.send_confirmation_message()

        self.assertEquals(1, session.query(PasswordReset).filter_by(user_id=1).count())
        self.assertEquals(response.location, 'http://example.com')
        self.assertEquals(view.request.session.pop_flash(),
            [u'Password retrieval instructions have been emailed to you.'])
        self.assertEquals(send_mail.call_args[0][0], u'*****@*****.**')
        self.assertEquals(send_mail.call_args[0][1], [u'*****@*****.**'])
        self.failUnless(isinstance(send_mail.call_args[0][2], Message))
Example #19
0
def identity(request):
    """OpenID identity page for a user.

    The URL this page is served as is the OpenID identifier for the user and
    the contents contain the required information for service discovery.
    """
    if request.matchdict.get('local_id', '').strip():
        session = DBSession()
        account = session.query(User)\
                    .filter(User.username == request.matchdict['local_id'])\
                    .first()
        if account is None:
            raise NotFound()

        return {
            'title': _(u'Identity page'),
            'openid_endpoint': route_url('openid_endpoint', request),
            'xrds_location': route_url('yadis_user', request, local_id=request.matchdict['local_id']),
            'identity': identity_url(request, request.matchdict['local_id']),
            }
    else:
        raise NotFound()
def login(request):
    """Renders a login form and logs in a user if given the correct
    credentials.
    """
    session = DBSession()
    login_url = route_url('login', request)

    login = u''

    if 'form.submitted' in request.POST:
        login = request.POST['login']

        if request.session.get_csrf_token() != request.POST.get('csrf_token'):
            raise Forbidden(u'CSRF attempt detected.')
        else:
            # Allow the use of the full identity URL.
            username = extract_local_id(request, login, relaxed=True)
            if len(username) == 0:
                # Fallback to the the local id.
                username = login

            user = session.query(User).filter_by(username=username).first()
            password = request.POST['password']

            if user is not None and user.check_password(password):
                headers = remember(request, user.username)
                request.session.flash(_(u'You have successfully logged in.'))
                return HTTPFound(location=request.application_url, headers=headers)

            request.session.flash(_(u'Login failed'))

    return {
        'title': _(u'Login'),
        'action_url': login_url,
        'login': login,
        'reset_url': route_url('reset_password', request),
        'csrf_token': request.session.get_csrf_token(),
    }
    def test_change_password__csrf_mismatch(self):
        from pyramid.exceptions import Forbidden
        from webidentity.views.user import change_password

        self.config.testing_securitypolicy(userid=u"dokai")
        self.config.add_route("change_password", "/change-password")

        user = User("dokai", "secret", "*****@*****.**")
        session = DBSession()
        session.add(user)
        request = testing.DummyRequest(
            post={
                "form.submitted": "1",
                "current_password": "******",
                "password": "******",
                "confirm_password": "******",
            }
        )
        token = request.session.new_csrf_token()
        request.POST["csrf_token"] = "invalid"

        self.failIf(token == "invalid")
        self.assertRaises(Forbidden, lambda: change_password(request))
    def test_prune_expired(self):
        from webidentity.models import PasswordReset

        view = self._makeView()
        session = DBSession()

        session.add(PasswordReset(1, datetime.now() - timedelta(days=30)))
        session.add(PasswordReset(2, datetime.now() + timedelta(days=30)))

        self.assertEquals(2, session.query(PasswordReset).count())
        view.prune_expired()
        self.assertEquals(1, session.query(PasswordReset).count())
        self.assertEquals(2, session.query(PasswordReset).first().user_id)
    def test_password_change_form(self):
        from webidentity.models import PasswordReset
        from webidentity.models import User

        view = self._makeView()
        session = DBSession()

        user = User(u'john.doe', u'secret', u'*****@*****.**')
        session.add(user)

        reset = PasswordReset(1, datetime.now() + timedelta(days=7), u'uniquetoken')
        session.add(reset)

        self.assertEquals(1, session.query(PasswordReset).filter_by(token=reset.token).count())
        self.assertEquals(u'john.doe', session.query(User).get(user.id).username)

        view.request.matchdict['token'] = reset.token
        self.assertEquals(view.password_change_form(), {
            'action_url': 'http://example.com/reset-password/process',
            'token': u'uniquetoken',
            'title': u'Change password'})
    def test_log_activity__with_url(self):
        from webidentity.views.user import log_activity

        self.config.testing_securitypolicy(userid=u"dokai")
        session = DBSession()
        self.assertEquals(0, session.query(Activity).count())
        user = User("dokai", "secret", "*****@*****.**")
        session.add(user)

        request = testing.DummyRequest(environ={"REMOTE_ADDR": "1.2.3.4"}, cookies={"auth_tkt": "sessiontoken"})
        log_activity(request, Activity.LOGIN, "http://www.rp.com")

        self.assertEquals(1, session.query(Activity).count())
        log_entry = session.query(Activity).first()
        self.assertEquals(user.id, log_entry.user_id)
        self.assertEquals("1.2.3.4", log_entry.ipaddr)
        self.assertEquals("sessiontoken", log_entry.session)
        self.assertEquals(Activity.LOGIN, log_entry.action)
        self.assertEquals("http://www.rp.com", log_entry.url)
    def DISABLED__test_cascading_delete(self):
        from webidentity.models import Persona
        from webidentity.models import User
        from webidentity.models import UserAttribute
        from webidentity.models import VisitedSite

        user = User(u'john.doe', u'secret', u'*****@*****.**')
        user.personas.append(
            Persona(u'Test persönä', attributes=[
                UserAttribute(type_uri, value)
                for type_uri, value
                in DUMMY_USER_ATTRIBUTES.iteritems()]))
        user.personas.append(
            Persona(u'Reversed persönä', attributes=[
                UserAttribute(type_uri, ''.join(reversed(value)))
                for type_uri, value
                in DUMMY_USER_ATTRIBUTES.iteritems()]))

        site1 = VisitedSite('http://www.rp.com', remember=False)
        site2 = VisitedSite('http://www.plone.org', remember=True)
        site2.persona = user.personas[0]

        user.visited_sites.append(site1)
        user.visited_sites.append(site2)

        session = DBSession()
        session.add(user)
        session.flush()

        self.assertEquals(2, len(session.query(User).get(1).personas))
        self.assertEquals(2, len(session.query(User).get(1).visited_sites))
        self.assertEquals(6, len(session.query(User).get(1).personas[0].attributes))

        session.query(User).filter_by(username=u'john.doe').delete()
        session.flush()

        self.assertEquals(0, session.query(User).count())
        self.assertEquals(0, session.query(Persona).count())
        self.assertEquals(0, session.query(UserAttribute).count())
        self.assertEquals(0, session.query(VisitedSite).count())
    def test_visited_sites__not_empty(self):
        from webidentity.views.user import visited_sites

        self.config.testing_securitypolicy(userid=u"dokai")

        session = DBSession()
        user = User("dokai", "secret", "*****@*****.**")

        user.personas.append(
            Persona(
                u"Test persönä",
                attributes=[UserAttribute(type_uri, value) for type_uri, value in DUMMY_USER_ATTRIBUTES.iteritems()],
            )
        )

        site1 = VisitedSite("http://www.rp.com", remember=False)
        site2 = VisitedSite("http://www.plone.org", remember=True)
        site2.persona = user.personas[0]

        user.visited_sites.append(site1)
        user.visited_sites.append(site2)

        user.activity.append(
            Activity(
                ipaddr="1.2.3.4",
                session="session1",
                action=Activity.AUTHORIZE_ONCE,
                url="http://www.rp.com",
                timestamp=datetime(2010, 1, 15, 12, 0),
            )
        )
        user.activity.append(
            Activity(
                ipaddr="2.3.4.5",
                session="session2",
                action=Activity.AUTHORIZE_ONCE,
                url="http://www.plone.org",
                timestamp=datetime(2010, 3, 24, 15, 23),
            )
        )
        user.activity.append(
            Activity(
                ipaddr="2.3.4.5",
                session="session3",
                action=Activity.AUTHORIZE,
                url="http://www.plone.org",
                timestamp=datetime(2010, 11, 15, 17, 9),
            )
        )

        session.add(user)

        request = testing.DummyRequest()
        self.assertEquals(
            visited_sites(request),
            {
                "action_url": "form_action",
                "sites": [
                    {
                        "url": u"http://www.plone.org",
                        "timestamp": "15.11.2010 17:09",
                        "persona": {"id": 1, "edit_url": "http://fo.bar/", "name": u"Test persönä"},
                        "id": 2,
                        "remember": "checked",
                    },
                    {
                        "url": u"http://www.rp.com",
                        "timestamp": "15.01.2010 12:00",
                        "persona": None,
                        "id": 1,
                        "remember": None,
                    },
                ],
            },
        )
class PasswordResetView(object):
    """Password reset logic."""

    def __init__(self, request):
        self.request = request
        self.session = DBSession()
        self.prune_expired()

    def prune_expired(self):
        """Prunes password reset requests that have expired."""
        self.session.query(PasswordReset)\
                .filter(PasswordReset.expires < datetime.now())\
                .delete()

    def render_form(self):
        """Renders the password reset form."""
        return {
            'action_url': route_url('reset_password_initiate', self.request),
            'title': _(u'Reset password'),
        }

    def password_change_form(self):
        """Renders the form for changing a password for a valid token."""
        reset = self.session.query(PasswordReset)\
            .filter(PasswordReset.token == self.request.matchdict['token'])\
            .filter(PasswordReset.expires >= datetime.now())\
            .first()
        if reset is None:
            # No matching password reset found
            raise NotFound()

        user = self.session.query(User).get(reset.user_id)
        if user is None:
            raise NotFound()

        return {
            'action_url': route_url('reset_password_process', self.request),
            'title': _(u'Change password'),
            'token': reset.token,
            }

    def send_confirmation_message(self):
        """Sends an email confirmation message to the user."""
        username = self.request.POST.get('username', '').strip()
        redirect_url = route_url('reset_password', self.request)
        if not username:
            self.request.session.flash(_(u'Please supply a username.'))
        else:
            user = self.session.query(User).filter(User.username == username).first()
            if user is None:
                self.request.session.flash(_(u'The given username does not match any account.'))
            else:
                # Create a password reset request that is valid for 24 hours
                reset = PasswordReset(user.id, datetime.now() + timedelta(hours=24))
                self.session.add(reset)
                message = self.create_message(user, reset)
                from_address = self.request.registry.settings['webidentity_from_address'].strip()
                send_mail(from_address, [user.email], message)
                self.request.session.flash(_(u'Password retrieval instructions have been emailed to you.'))
                redirect_url = self.request.application_url

        return HTTPFound(location=redirect_url)

    def create_message(self, user, reset):
        """Returns an email.message.Message object representing the password
        reset message.
        """
        from_address = self.request.registry.settings['webidentity_from_address'].strip()
        date_format = self.request.registry.settings['webidentity_date_format'].strip()
        locale = get_localizer(self.request)
        subject = locale.translate(
            _(u'Password reset for ${identity}',
            mapping={'identity': identity_url(self.request, user.username)}))

        message = Message()
        message['From'] = Header(from_address, 'utf-8')
        message['To'] = Header(u'{0} <{1}>'.format(user.username, user.email), 'utf-8')
        message['Subject'] = Header(subject, 'utf-8')

        message.set_payload(locale.translate(_(
            u'password-reset-email',
            default=textwrap.dedent(u'''
            Hi ${username}

            A password retrieval process has been initiated for your OpenID
            identity

              ${identity}

            If the process was initiated by you you can continue the process
            of resetting your password by opening the following link in your
            browser

              ${reset_url}

            The link will will expire at ${expiration}.

            If you did not initiate this request you can just ignore this
            email. Your password has not been changed.
            ''').lstrip(),
            mapping=dict(
                username=user.username,
                identity=identity_url(self.request, user.username),
                expiration=reset.expires.strftime(date_format),
                reset_url=route_url('reset_password_token', self.request, token=reset.token)))))

        return message

    def change_password(self):
        """Changes the password for a user."""
        token = self.request.POST.get('token', '').strip()

        if token:
            password = self.request.POST.get('password', '')
            confirm_password = self.request.POST.get('confirm_password', '')
            if len(password.strip()) < 5:
                self.request.session.flash(_(u'Password must be at least five characters long'))
                return HTTPFound(
                    location=route_url('reset_password_token', self.request, token=token))
            elif password != confirm_password:
                self.request.session.flash(_(u'Given passwords do not match'))
                return HTTPFound(
                    location=route_url('reset_password_token', self.request, token=token))
            else:
                reset = self.session.query(PasswordReset)\
                    .filter(PasswordReset.token == token)\
                    .filter(PasswordReset.expires >= datetime.now())\
                    .first()
                if reset is None:
                    raise NotFound()

                user = self.session.query(User).get(reset.user_id)
                if user is None:
                    raise NotFound()

                # Update the user password
                user.password = password
                self.session.add(user)
                self.session.delete(reset)

                self.request.session.flash(_(u'Password changed.'))
                headers = remember(self.request, user.username)
                return HTTPFound(location=self.request.application_url, headers=headers)

        raise NotFound()
 def tearDown(self):
     testing.tearDown()
     DBSession.remove()
 def __init__(self, request):
     self.request = request
     self.session = DBSession()
     self.prune_expired()
def populate_demo():
    """Populates the database with 50 demo users."""
    engine = engine_from_config(get_config(), 'sqlalchemy.')
    initialize_sql(engine, False)

    session = DBSession()

    for count in range(1, 51):
        session.add(User('test.user{0:02}'.format(count), 'testi', '*****@*****.**'))

    session.add(User('test.pref01', 'testi', u'*****@*****.**'))
    session.add(User('test.pref02', 'testi', None))
    session.add(User('test.pref03', 'testi', u'*****@*****.**'))
    session.add(User('test.pref04', 'testi', u'*****@*****.**'))
    session.add(User('test.pref05', 'testi', None))

    session.flush()
    transaction.commit()
    print("Generated 50 demo users.")