Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit

  • Loading branch information...
commit 4ce43a4dfc6f6f862f6bfd61632d5822daae7c76 0 parents
Thomas Hill authored
1  MANIFEST.in
@@ -0,0 +1 @@
+include *.txt
275 README.txt
@@ -0,0 +1,275 @@
+pyramid_openid provides a view for the Pyramid framework that acts as an OpenID consumer.
+
+This code is offered under the BSD-derived Repoze Public License.
+
+Much of this code was inspired by (read: 'lifted from') the repoze.who.plugins.openid
+code which can be found here:
+http://quantumcore.org/docs/repoze.who.plugins.openid
+
+In your Pyramid app, add the pyramid_openid.verify_openid view_callable to your
+view_configuration, and add the needed settings to your .ini file.
+
+Here is a barebones setup:
+openid.store.type = file
+openid.store.file.path = %(here)s/sstore
+openid.success_callback = myapp.lib:remember_me
+
+This setup requires you have a folder in your app directory called 'sstore',
+and that you have a callback function in your lib module named "remember_me".
+Remember me will receive the current request and the other information returned
+from the OpenID provider, and will then do whatever is needed to remember the user,
+load metadata into the session - that part is completely up to the coder.
+
+This setup will then assume the defaults for the rest of the keys.
+
+Once the configuration is in place, it's time to hook up the view to the application.
+You can do this however you want to.
+
+Example:
+In your app config setup code, add this line before 'return config.make_wsgi_app()'
+
+config.add_route('verity_openid',
+ pattern='/dologin.html',
+ view='pyramid_openid.verify_openid')
+
+Now you have a url to submit your OpenID form to: /dologin.html.
+Based on the configuration above, it expects to find the user's OpenID URL
+in request.params['openid'].
+
+
+REQUIRED SETTINGS
+=================
+OpenID Data Store
+-----------------
+Key:
+openid.store.type
+
+Description:
+This determines the type of store python-openid will use
+to track nonces and other cross request data. Note that
+this defaults to None, which has python-openid use a
+stateless request type. Stateless mode isn't reliable;
+something else should be chosen. File and mem are
+recommended.
+
+The SQL store has yet to be tested or even verified
+working. It is also not recommended.
+
+Default:
+None
+
+Examples:
+To use a file store:
+(openid.store.file.path must also be specified)
+openid.store.type = file
+
+To use a memory store:
+openid.store.type = mem
+
+To use a sql store:
+(openid.store.sql.connection_string and
+openid.store.sql.associations_table must also
+be specified)
+THIS IS NOT TESTED AND DOES NOT WORK
+openid.store.type = sql
+
+
+OPTIONAL SETTINGS:
+==================
+Successful Login Destination URL
+--------------------------------
+Key:
+openid.success_destination
+
+Description:
+This is the url that the user will be sent to after they've
+successfully been verified by the OpenID provider.
+
+Default:
+/
+
+Example:
+To have your user be sent to their profile page upon successful
+login:
+openid.success_destination = http://www.example.com/profile.html
+
+Successful Login Callback
+-------------------------
+Key:
+openid.success_callback
+
+Description:
+This is a callable that will be called upon successful verification
+by the OpenID provider. The callable will be passed two parameters;
+the current request, and a dictionary with the following structure:
+{
+ 'identity_url': The user's unique URL from the provider,
+ 'ax': A dictionary containing all the returned
+ active exchange parameters requested,
+ 'sreg': A list containing all the returned
+ simple registration parameters requested
+}
+
+This callback is required to have the following format:
+module.optional_submodules:function
+
+Default:
+None
+
+Example:
+If the callback is in the lib module of the my app package, and
+is named openid_callback, then this is the setting to be used:
+openid.success_callback = myapp.lib:openid_callback
+
+AX
+--
+Keys:
+openid.ax_required
+openid.ax_optional
+
+Description:
+These represent user data requested via OpenID Attribute Exchange.
+
+Default:
+None
+
+Example:
+To require that the provider respond with the user's email:
+openid.ax_required = email=http://schema.openid.net/contact/email
+
+SX
+--
+Keys:
+openid.sreg_required
+openid.sreg_optional
+
+Description:
+These represent user data requested via OpenID Simple Registration.
+
+Default:
+None
+
+Example:
+To require that the provider respond with the user's email:
+openid.sreg_required = email
+
+Incoming OpenID Param Name:
+---------------------------
+Key:
+openid.param_field_name
+
+Description:
+When a request is first submitted with the user's OpenID URL,
+it must be retrieved from request.params with a key.
+This is the name of that key in request.params.
+
+Default:
+openid
+
+Examples:
+Once submitted, the user's OpenID URL will be found in
+request.params['users_openid_url']:
+openid.param_field_name = users_openid_url
+
+Error Destination
+-----------------
+Key:
+openid.error_destination
+
+Description:
+When something in the OpenID verification process fails,
+the user will be sent to this url. The error message
+will be stored in the request.session.flash queue
+as specified by openid.error_flash_queue
+
+Default:
+request.referrer
+
+Example:
+To send the user to a http://www.example.com/sorry.html upon failure:
+openid.error_destination = http://www.example.com/sorry.html
+
+Error Flash Queue
+-----------------
+Key:
+openid.error_flash_queue
+
+Description:
+If something goes awry in the OpenID process, the error message
+will be put in the request.session.flash message queue, and this
+is the name of that queue.
+
+Default:
+The default flash queue ('')
+
+Example:
+To put the error messages in the 'OpenIDErrors' flash queue:
+openid.error_flash_queue=OpenIDErrors
+
+Realm Name
+----------
+Key:
+openid.realm_name
+
+Description:
+This is the value of the realm parameter passed to the OpenID
+provider. It's here for the sake of completeness, but unless
+you know what you're doing there's no reason to change it.
+
+Default:
+request.host_url
+
+Example:
+To set the Realm to 'www.example.com':
+openid.realm_name = http://www.example.com
+
+Note:
+Changing the realm_name will most likely cause your request
+to fail.
+
+
+CONDITIONAL SETTINGS
+====================
+File Store Path
+---------------
+Key:
+openid.store.file.path
+
+Description:
+If the file store path is to be used, then it needs
+a writable folder to store data into. This is that path.
+
+Default:
+No default
+
+Example:
+To store data in the folder named "sstore" in the same
+folder as your development.ini:
+(Note that you must make this directory as well)
+openid.store.file.path = %(here)s/sstore
+
+SQL Connection String
+---------------------
+Key:
+openid.store.sql.connection_string
+
+Description:
+This is the connection string for the database for
+python-openid to store its temporary data in.
+THIS HAS NOT BEEN TESTED AND DOES NOT WORK YET.
+
+Default:
+No default
+
+SQL Associations Table
+----------------------
+Key:
+openid.store.sql.associations_table
+
+Description:
+This is the name of the table that python-openid
+will store is temporary data in.
+THIS HAS NOT BEEN TESTED AND DOES NOT WORK YET.
+
+Default:
+No default
1  pyramid_openid/__init__.py
@@ -0,0 +1 @@
+from view import verify_openid
BIN  pyramid_openid/__init__.pyc
Binary file not shown
181 pyramid_openid/view.py
@@ -0,0 +1,181 @@
+"""
+TODO:
+ Dependencies:
+ pyramid
+ openid
+ logging
+ pyramid app must have sessions enabled
+ Make this its own package
+ Write sphinx docs
+ explain user flow
+ explain how callback works;
+ this is not an authn policy
+ explain all options
+ Write tests
+ request with no openid field
+ request with openid field that doesn't resolve
+ request with openid field that comes back successful
+ request with openid field that comes back successful
+ and calls callback
+"""
+import openid
+from openid.store import memstore, filestore, sqlstore
+from openid.consumer import consumer
+from openid.oidutil import appendArgs
+from openid.cryptutil import randomString
+from openid.fetchers import setDefaultFetcher, Urllib2Fetcher
+from openid.extensions import pape, sreg, ax
+
+from pyramid.httpexceptions import HTTPFound
+from pyramid.security import remember
+
+import logging
+
+log = logging.getLogger(__name__)
+
+
+def verify_openid(request):
+ settings = request.registry.settings
+ openid_field = settings.get('openid.param_field_name', 'openid')
+ log.info('OpenID Field to search for: %s' % openid_field)
+ incoming_openid_url = request.params.get(openid_field, None)
+ openid_mode = request.params.get('openid.mode', None)
+ if incoming_openid_url is not None:
+ return process_incoming_request(request, incoming_openid_url)
+ elif openid_mode == 'id_res':
+ return process_provider_response(request)
+
+
+def worthless_callback(request, success_dict):
+ pass
+
+
+def build_consumer_from_request(request):
+ settings = request.registry.settings
+ store_type = settings.get('openid.store.type')
+ log.info('Store type to use: %s' % store_type)
+ store = None
+ if store_type == 'file':
+ store_file_path = settings.get('openid.store.file.path')
+ log.info('File Store Path: %s' % store_file_path)
+ store = filestore.FileOpenIDStore(store_file_path)
+ elif store_type == 'mem':
+ store = memstore.MemoryStore()
+ elif store_type == 'sql':
+ # TODO: This does not work as we need a connection, not a string
+ sql_connstring = settings.get('openid.store.sql.connection_string')
+ sql_associations_table = settings.get(
+ 'openid.store.sql.associations_table')
+ store = sqlstore.SQLStore(sql_connstring,
+ sql_associations_table,
+ sql_connstring)
+ log.info('Store: %s' % store)
+ openid_consumer = consumer.Consumer(request.session, store)
+ return openid_consumer
+
+
+def process_incoming_request(request, incoming_openid_url):
+ settings = request.registry.settings
+ log.info('OpenID URL supplied by user: %s' % incoming_openid_url)
+ openid_consumer = build_consumer_from_request(request)
+ try:
+ openid_request = openid_consumer.begin(incoming_openid_url)
+ ax_required = settings.get('openid.ax_required', {})
+ ax_optional = settings.get('openid.ax_optional', {})
+ log.info('ax_required: %s' % ax_required)
+ log.info('ax_optional: %s' % ax_optional)
+ if len(ax_required.values()) or len(ax_optional.values()):
+ fetch_request = ax.FetchRequest()
+ for value in ax_required.values():
+ fetch_request.add(ax.AttrInfo(value, required=True))
+ for value in ax_optional.values():
+ fetch_request.add(ax.AttrInfo(value, required=False))
+ openid_request.addExtension(fetch_request)
+
+ sreg_required = settings.get('openid.sreg_required', [])
+ sreg_optional = settings.get('openid.sreg_optional', [])
+ log.info('sreg_required: %s' % sreg_required)
+ log.info('sreg_optional: %s' % sreg_optional)
+ if len(sreg_required) or len(sreg_optional):
+ sreq = sreg.SRegRequest(required=sreg_required,
+ optional=sreg_optional)
+ openid_request.addExtension(sreq)
+ except consumer.DiscoveryFailure, exc:
+ # eventually no openid server could be found
+ return error_to_login_form(request, 'Error in discovery: %s' % exc[0])
+ except KeyError, exc:
+ # TODO: when does that happen, why does plone.openid use "pass" here?
+ return error_to_login_form(request, 'Error in discovery: %s' % exc[0])
+ # not sure this can still happen but we are making sure.
+ # should actually been handled by the DiscoveryFailure exception above
+ if openid_request is None:
+ return error_to_login_form(
+ request,
+ 'No OpenID services found for %s' % incoming_openid_url)
+ #Not sure what the point of setting this to anything else is
+ realm_name = settings.get('openid.realm_name', request.host_url)
+ return_url = request.url
+ redirect_url = openid_request.redirectURL(realm_name, return_url)
+ log.info('Realm Name: %s' % realm_name)
+ log.info('Return URL from provider will be: %s' % return_url)
+ log.info('Redirecting to: %s' % redirect_url)
+ return HTTPFound(location=redirect_url)
+
+
+def process_provider_response(request):
+ settings = request.registry.settings
+ openid_consumer = build_consumer_from_request(request)
+ info = openid_consumer.complete(request.params, request.url)
+ log.info('OpenID Info Status: %s' % info.status)
+ if info.status == consumer.SUCCESS:
+ log.info('OpenID login successful.')
+ success_dict = {
+ 'identity_url': info.identity_url,
+ 'ax': {},
+ 'sreg': []}
+ fr = ax.FetchResponse.fromSuccessResponse(info)
+ if fr is not None:
+ ax_required = settings.get('openid.ax_required', {})
+ ax_optional = settings.get('openid.ax_optional', {})
+ items = chain(ax_required.items(), ax_optional.items())
+ for key, value in items:
+ try:
+ success_dict['ax'][key] = fr.get(value)
+ except KeyError:
+ pass
+ fr = sreg.SRegResponse.fromSuccessResponse(info)
+ if fr is not None:
+ sreg_required = settings.get('openid.sreg_required', [])
+ sreg_optional = settings.get('openid.sreg_optional', [])
+ items = chain(sreg_required, sreg_optional)
+ for key in items:
+ try:
+ success_dict['sreg'][key] = fr.get(value)
+ except KeyError:
+ pass
+
+ callback = settings.get('openid.success_callback', None)
+ if callback is not None:
+ log.info('Callback for storing result: %s' % callback)
+ #Isn't there a better/standard way to parse
+ #module.submodule:functions, or is this it?
+ callback = callback.split(':')
+ #TODO: Use pyramid.util.DottedNameResolver?
+ callback_module = __import__(callback[0], fromlist=[callback[1]])
+ callback_function = getattr(callback_module, callback[1])
+ else:
+ callback_function = worthless_callback
+ callback_function(request, success_dict)
+ success_destination = settings.get('openid.success_destination', '/')
+ return HTTPFound(location=success_destination)
+
+
+def error_to_login_form(request, message):
+ log.info('OpenID ERROR: %s' % message)
+ settings = request.registry.settings
+ error_url = settings.get('openid.error_destination', request.referrer)
+ if error_url is None:
+ error_url = '/'
+ error_flash_queue = settings.get('openid.error_flash_queue', '')
+ request.session.flash(message, error_flash_queue)
+ return HTTPFound(location=error_url)
BIN  pyramid_openid/view.pyc
Binary file not shown
24 setup.py
@@ -0,0 +1,24 @@
+import os
+from setuptools import setup, find_packages
+version = '0.1.0'
+README = os.path.join(os.path.dirname(__file__), 'README.txt')
+long_description = open(README).read() + 'nn'
+
+setup(name='pyramid_openid',
+ version=version,
+ description=('A view for pyramid that functions as an '
+ 'OpenID consumer.'),
+ long_description=long_description,
+ classifiers=['Framework :: Pylons',
+ 'Intended Audience :: Developers'
+ 'License :: Repoze Public License'
+ 'Programming Language :: Python'
+ 'Topic :: Internet :: WWW/HTTP'
+ 'Topic :: Internet :: WWW/HTTP :: WSGI'],
+ keywords='pyramid openid',
+ author='tomlikestorock',
+ author_email='tomlikestorock@gmail.com',
+ license='BSD-derived (http://www.repoze.org/LICENSE.txt)',
+ packages=find_packages(),
+ install_requires=['pyramid', 'python-openid']
+)
Please sign in to comment.
Something went wrong with that request. Please try again.