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.'])
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)
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)
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))
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.")