Permalink
Browse files

First pass.

  • Loading branch information...
0 parents commit 6076c6b97269b6477ce6389693bb3a4618df9e2f @tseaver tseaver committed Dec 12, 2010
@@ -0,0 +1,5 @@
+Data.fs*
+venv
+*.egg-info
+cartouche/templates/*.py
+docs/_build
@@ -0,0 +1,8 @@
+``cartouche`` Changelog
+=======================
+
+
+0.1 (unreleased)
+----------------
+
+- Initial release.
@@ -0,0 +1,3 @@
+Copyright (c) 2010 Agendaless Consulting and Contributors.
+(http://www.agendaless.com), All Rights Reserved
+
@@ -0,0 +1,41 @@
+License
+
+ A copyright notice accompanies this license document that identifies
+ the copyright holders.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ 1. Redistributions in source code must retain the accompanying
+ copyright notice, this list of conditions, and the following
+ disclaimer.
+
+ 2. Redistributions in binary form must reproduce the accompanying
+ copyright notice, this list of conditions, and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ 3. Names of the copyright holders must not be used to endorse or
+ promote products derived from this software without prior
+ written permission from the copyright holders.
+
+ 4. If any files are modified, you must cause the modified files to
+ carry prominent notices stating that you changed the files and
+ the date of any change.
+
+ Disclaimer
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND
+ ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
@@ -0,0 +1,36 @@
+``cartouche`` README
+====================
+
+This package provides a set of applications which can be used to drive
+"registration-based" sites:
+
+- registration
+
+- login / logout
+
+- change password
+
+- password recovery
+
+- profile editing
+
+- user / group administration
+
+
+``cartouche`` is built on the following components:
+
+- The ``cartouche`` applications run atop the ``pyramid`` framework, using
+ ``chameleon`` for their templating, and ``deform`` for form schema /
+ validation handling.
+
+- ``cartouche`` stores users, profiles, and groups in a ``ZODB`` database.
+
+- ``cartouche`` uses ``zope.password`` to do password hashing / checking.
+
+- ``cartouche`` plugs into ``repoze.who`` as an authenticator, a challenger,
+ and a metadata provider.
+
+Please see ``docs/index.rst`` for the documentation, which can also be
+read online at:
+
+ http://packages.python.org/cartouche
@@ -0,0 +1,58 @@
+``cartouche`` TOODs
+===================
+
+
+Registration
+------------
+
+- [_] Define "minimal" registration schema using ``deform``.
+
+ o ``email`` plus "security question" pair are initially-required fields.
+
+ o Seve entered data, along with a quasi-random token and an expiration
+ date, in a persistent mapping keyed by e-mail address.
+
+ o Redirect to URL with 'email' in query string.
+
+ o Send confirmation e-mail with token to be cut and pasted into
+ the second page.
+
+ o Include a link to the confirm page in the mail: if the user visits
+ the link, rather than leaving the browser window open, require that
+ they re-enter the security question and answer (?)?
+
+ o Allow configuration of additional registration-tie schema elements,
+ to be collected after e-mail is confirmed. Or maybe just redirect
+ to the profile edit view?
+
+- [_] Add e-mail-confirmed user to ``ZODB`` user store
+
+ o Allow user to pick login name, defaulting to e-mail address.
+
+ o Hash password using ``zope.password`` utility.
+
+
+Login / Logout
+--------------
+
+- [_] Implement view following the ``repoze.who`` 2.0 docs.
+
+ o Compare passwords using ``zope.password`` utility.
+
+
+Password Recovery / Reset
+-------------------------
+
+- [_] TBD
+
+
+Profile Editing
+---------------
+
+- [_] Use configured "full" registration schema defined using ``deform``.
+
+
+User / Group Administration
+---------------------------
+
+- [_] TBD
@@ -0,0 +1,18 @@
+from pyramid.configuration import Configurator
+from repoze.zodbconn.finder import PersistentApplicationFinder
+from cartouche.models import appmaker
+
+def main(global_config, **settings):
+ """ This function returns a Pyramid WSGI application.
+ """
+ zodb_uri = settings.get('zodb_uri')
+ zcml_file = settings.get('configure_zcml', 'configure.zcml')
+ if zodb_uri is None:
+ raise ValueError("No 'zodb_uri' in application configuration.")
+
+ finder = PersistentApplicationFinder(zodb_uri, appmaker)
+ def get_root(request):
+ return finder(request.environ)
+ config = Configurator(root_factory=get_root, settings=settings)
+ config.load_zcml(zcml_file)
+ return config.make_wsgi_app()
@@ -0,0 +1,30 @@
+<configure xmlns="http://pylonshq.com/pyramid">
+
+ <!-- this must be included for the view declarations to work -->
+ <include package="pyramid.includes" />
+
+ <view
+ context=".models.Root"
+ view=".registration.register_view"
+ renderer="templates/register.pt"
+ name="register.html"
+ />
+
+ <view
+ context=".models.Root"
+ view=".registration.confirm_registration_view"
+ renderer="templates/register.pt"
+ name="confirm_registration.html"
+ />
+
+ <static
+ name="static"
+ path="static"
+ />
+
+ <static
+ name="deform"
+ path="deform:static"
+ />
+
+</configure>
@@ -0,0 +1,8 @@
+from zope.interface import Interface
+
+class ITokenGenerator(Interface):
+ """ Utility interface: generate tokens for confirmation e-mails.
+ """
+ def getToken():
+ """ Return a unique, quasi-random token as a string.
+ """
@@ -0,0 +1,12 @@
+from persistent.mapping import PersistentMapping
+
+class Root(PersistentMapping):
+ __parent__ = __name__ = None
+
+def appmaker(zodb_root):
+ if not 'app_root' in zodb_root:
+ app_root = Root()
+ zodb_root['app_root'] = app_root
+ import transaction
+ transaction.commit()
+ return zodb_root['app_root']
@@ -0,0 +1,144 @@
+##############################################################################
+#
+# Copyright (c) 2010 Agendaless Consulting and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the BSD-like license at
+# http://www.repoze.org/LICENSE.txt. A copy of the license should accompany
+# this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL
+# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND
+# FITNESS FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+from email.message import Message
+from pkg_resources import resource_filename
+
+from colander import Email
+from colander import Schema
+from colander import SchemaNode
+from colander import String
+from deform import Form
+from deform.template import default_dir as deform_templates_dir
+from deform.widget import SelectWidget
+from deform.widget import TextInputWidget
+from pyramid.renderers import get_renderer
+from pyramid.url import model_url
+from repoze.sendmail.interfaces import IMailDelivery
+from repoze.sendmail.delivery import DirectMailDelivery
+from repoze.sendmail.mailer import SMTPMailer
+from webob.exc import HTTPFound
+
+from cartouche.interfaces import ITokenGenerator
+
+
+templates_dir = resource_filename('cartouche', 'templates/')
+
+Form.set_zpt_renderer([templates_dir, deform_templates_dir])
+
+QUESTIONS = [
+ ('color', 'What is your favorite color?'),
+ ('borncity', 'In what city were you born?'),
+ ('petname', 'What was the name of your favorite childhood pet?'),
+]
+
+class SecurityQuestion(Schema):
+ question = SchemaNode(String(), widget=SelectWidget(values=QUESTIONS))
+ answer = SchemaNode(String())
+
+class Signup(Schema):
+ email = SchemaNode(String(), validator=Email())
+ security = SecurityQuestion(title=" ")
+
+class ReadonlyTextWidget(TextInputWidget):
+ template = readonly_template = 'readonly_text_input'
+
+class Confirm(Schema):
+ email = SchemaNode(String(),
+ validator=Email(),
+ widget=ReadonlyTextWidget(),
+ )
+ token = SchemaNode(String(),
+ description="Enter the token from the registration "
+ "confirmation e-mail you received.")
+
+def _randomToken(request):
+ generator = request.registry.queryUtility(ITokenGenerator)
+ if generator:
+ return generator.getToken()
+ return 'XYZZY' # XXX
+
+
+# By default, deliver e-mail via localhost, port 25.
+_delivery = DirectMailDelivery(SMTPMailer())
+
+REGISTRATION_EMAIL = """
+Thank you for registering.
+
+In your browser, please copy and paste the following string
+into the 'Token' field:
+
+ %(token)s
+
+If you do not still have that page open, you can visit it via
+this URL (you will need to re-enter the same security question and
+answer as you used on the initial registration form):
+
+ %(confirmation_url)s
+
+Once you have entered the token, click the "Confirm" button to
+complete your registration.
+"""
+
+def register_view(context, request):
+ if 'register' in request.POST:
+ email = request.POST['email']
+ security = request.POST['security']
+ token = security['token'] = _randomToken(request)
+ pending_registrations = context.users.pending_registrations
+ pending_registrations[email] = security
+ from_addr = request.registry.settings['from_addr']
+ delivery = request.registry.queryUtility(IMailDelivery) or _delivery
+ confirmation_url = model_url(context, request, request.view_name,
+ query=dict(email=email))
+ body = REGISTRATION_EMAIL % {'token': token,
+ 'confirmation_url': confirmation_url}
+ message = Message()
+ message.set_payload(body)
+ delivery.send(from_addr, [email], message)
+ confirmation_url = model_url(context, request, request.view_name,
+ query=dict(email=email))
+ return HTTPFound(location=confirmation_url)
+
+ main_template = get_renderer('templates/main.pt')
+ return {'main_template': main_template.implementation(),
+ 'form': Form(Signup(), buttons=('register',)),
+ 'appstruct': None,
+ }
+
+
+def confirm_registration_view(context, request):
+ if 'confirm' in request.POST:
+ email = request.POST['email']
+ security = request.POST['security']
+ token = security['token'] = _randomToken(request)
+ pending_registrations = context.users.pending_registrations
+ pending_registrations[email] = security
+ from_addr = request.registry.settings['from_addr']
+ delivery = request.registry.queryUtility(IMailDelivery) or _delivery
+ confirmation_url = model_url(context, request, request.view_name,
+ query=dict(email=email))
+ body = REGISTRATION_EMAIL % {'token': token,
+ 'confirmation_url': confirmation_url}
+ message = Message()
+ message.set_payload(body)
+ delivery.send(from_addr, [email], message)
+ confirmation_url = model_url(context, request, request.view_name,
+ query=dict(email=email))
+ return HTTPFound(location=confirmation_url)
+
+ main_template = get_renderer('templates/main.pt')
+ return {'main_template': main_template.implementation(),
+ 'form': Form(Confirm(), buttons=('confirm',)),
+ 'appstruct': {'email': request.GET['email']}
+ }
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.

0 comments on commit 6076c6b

Please sign in to comment.