1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/b9e188c4ecba/ changeset: b9e188c4ecba user: dan date: 2012-03-23 18:41:01 summary: Abstract OpenID providers to be defined outside of the Python code and to allow customization of actions. See individual examples in openid/ and the list of enabled OpenID providers in openid_conf.xml.sample. Feedback is welcomed. affected #: 17 files diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 lib/galaxy/app.py --- a/lib/galaxy/app.py +++ b/lib/galaxy/app.py @@ -13,6 +13,7 @@ from galaxy.tags.tag_handler import GalaxyTagHandler from galaxy.tools.imp_exp import load_history_imp_exp_tools from galaxy.sample_tracking import external_service_types +from galaxy.openid.providers import OpenIDProviders class UniverseApplication( object ): """Encapsulates the state of a Universe application""" @@ -105,6 +106,9 @@ if self.config.enable_openid: from galaxy.web.framework import openid_manager self.openid_manager = openid_manager.OpenIDManager( self.config.openid_consumer_cache_path ) + self.openid_providers = OpenIDProviders.from_file( self.config.openid_config ) + else: + self.openid_providers = OpenIDProviders() # Start the heartbeat process if configured and available if self.config.use_heartbeat: from galaxy.util import heartbeat diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 lib/galaxy/config.py --- a/lib/galaxy/config.py +++ b/lib/galaxy/config.py @@ -44,7 +44,9 @@ self.cookie_path = kwargs.get( "cookie_path", "/" ) # web API self.enable_api = string_as_bool( kwargs.get( 'enable_api', False ) ) + # Galaxy OpenID settings self.enable_openid = string_as_bool( kwargs.get( 'enable_openid', False ) ) + self.openid_config = kwargs.get( 'openid_config_file', 'openid_conf.xml' ) self.enable_quotas = string_as_bool( kwargs.get( 'enable_quotas', False ) ) self.tool_sheds_config = kwargs.get( 'tool_sheds_config_file', 'tool_sheds_conf.xml' ) self.enable_unique_workflow_defaults = string_as_bool( kwargs.get( 'enable_unique_workflow_defaults', False ) ) diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 lib/galaxy/openid/__init__.py --- /dev/null +++ b/lib/galaxy/openid/__init__.py @@ -0,0 +1,3 @@ +""" +OpenID functionality +""" \ No newline at end of file diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 lib/galaxy/openid/providers.py --- /dev/null +++ b/lib/galaxy/openid/providers.py @@ -0,0 +1,118 @@ +""" +Contains OpenID provider functionality +""" + +import logging, os +from galaxy.util import parse_xml, string_as_bool +from galaxy.util.odict import odict + + +log = logging.getLogger( __name__ ) + +class OpenIDProvider( object ): + '''An OpenID Provider object.''' + @classmethod + def from_file( cls, filename ): + return cls.from_elem( parse_xml( filename ).getroot() ) + @classmethod + def from_elem( cls, xml_root ): + provider_elem = xml_root + provider_id = provider_elem.get( 'id', None ) + provider_name = provider_elem.get( 'name', provider_id ) + op_endpoint_url = provider_elem.find( 'op_endpoint_url' ) + if op_endpoint_url is not None: + op_endpoint_url = op_endpoint_url.text + assert (provider_id and provider_name and op_endpoint_url), Exception( "OpenID Provider improperly configured" ) + sreg_required = [] + sreg_optional = [] + use_for = {} + store_user_preference = {} + use_default_sreg = True + for elem in provider_elem.findall( 'sreg' ): + use_default_sreg = False + for field_elem in elem.findall( 'field' ): + sreg_name = field_elem.get( 'name' ) + assert sreg_name, Exception( 'A name is required for a sreg element' ) + if string_as_bool( field_elem.get( 'required' ) ): + sreg_required.append( sreg_name ) + else: + sreg_optional.append( sreg_name ) + for use_elem in field_elem.findall( 'use_for' ): + use_for[ use_elem.get( 'name' ) ] = sreg_name + for store_user_preference_elem in field_elem.findall( 'store_user_preference' ): + store_user_preference[ store_user_preference_elem.get( 'name' ) ] = sreg_name + if use_default_sreg: + sreg_required = None + sreg_optional = None + use_for = None + return cls( provider_id, provider_name, op_endpoint_url, sreg_required, sreg_optional, use_for, store_user_preference ) + def __init__( self, id, name, op_endpoint_url, sreg_required=None, sreg_optional=None, use_for=None, store_user_preference=None ): + '''When sreg options are not specified, defaults are used.''' + self.id = id + self.name = name + self.op_endpoint_url = op_endpoint_url + if sreg_optional is None: + self.sreg_optional = [ 'nickname', 'email' ] + else: + self.sreg_optional = sreg_optional + if sreg_required: + self.sreg_required = sreg_required + else: + self.sreg_required = [] + if use_for is not None: + self.use_for = use_for + else: + self.use_for = {} + if 'nickname' in ( self.sreg_optional + self.sreg_required ): + self.use_for[ 'username' ] = 'nickname' + if 'email' in ( self.sreg_optional + self.sreg_required ): + self.use_for[ 'email' ] = 'email' + if store_user_preference: + self.store_user_preference = store_user_preference + else: + self.store_user_preference = {} + def post_authentication( self, trans, openid_manager, info ): + sreg_attributes = openid_manager.get_sreg( info ) + for store_pref_name, store_pref_value_name in self.store_user_preference.iteritems(): + if store_pref_value_name in ( self.sreg_optional + self.sreg_required ): + trans.user.preferences[ store_pref_name ] = sreg_attributes.get( store_pref_value_name ) + print 'setting',store_pref_name,'to',trans.user.preferences[ store_pref_name ] + else: + raise Exception( 'Only sreg is currently supported.' ) + trans.sa_session.add( trans.user ) + trans.sa_session.flush() + +class OpenIDProviders( object ): + '''Collection of OpenID Providers''' + @classmethod + def from_file( cls, filename ): + try: + return cls.from_elem( parse_xml( filename ).getroot() ) + except Exception, e: + log.error( 'Failed to load OpenID Providers: %s' % ( e ) ) + return cls() + @classmethod + def from_elem( cls, xml_root ): + oid_elem = xml_root + providers = odict() + for elem in oid_elem.findall( 'provider' ): + try: + provider = OpenIDProvider.from_file( os.path.join( 'openid', elem.get( 'file' ) ) ) + providers[ provider.id ] = provider + log.debug( 'Loaded OpenID provider: %s (%s)' % ( provider.name, provider.id ) ) + except Exception, e: + log.error( 'Failed to add OpenID provider: %s' % ( e ) ) + return cls( providers ) + def __init__( self, providers=None ): + if providers: + self.providers = providers + else: + self.providers = odict() + def __iter__( self ): + for provider in self.providers.itervalues(): + yield provider + def get( self, name, default=None ): + if name in self.providers: + return self.providers[ name ] + else: + return default diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 lib/galaxy/web/controllers/user.py --- a/lib/galaxy/web/controllers/user.py +++ b/lib/galaxy/web/controllers/user.py @@ -11,6 +11,7 @@ from galaxy.util.json import from_json_string, to_json_string from galaxy.web.framework.helpers import iff from galaxy.security.validate_user_input import validate_email, validate_publicname, validate_password +from galaxy.openid.providers import OpenIDProvider log = logging.getLogger( __name__ ) @@ -21,12 +22,6 @@ <p/> """ -OPENID_PROVIDERS = { 'Google' : 'https://www.google.com/accounts/o8/id', - 'Yahoo!' : 'http://yahoo.com', - 'AOL/AIM' : 'http://openid.aol.com', - 'Launchpad' : 'http://login.launchpad.net', - } - class UserOpenIDGrid( grids.Grid ): use_panels = False title = "OpenIDs linked to your account" @@ -68,19 +63,26 @@ if not referer: referer = url_for( '/' ) consumer = trans.app.openid_manager.get_consumer( trans ) - process_url = trans.request.base.rstrip( '/' ) + url_for( controller='user', action='openid_process', referer=referer, auto_associate=auto_associate ) - if not openid_url and openid_provider and openid_provider in OPENID_PROVIDERS: - openid_url = OPENID_PROVIDERS[openid_provider] - if openid_url: + openid_provider_obj = None + if not openid_url and openid_provider and trans.app.openid_providers.get( openid_provider ): + openid_provider_obj = trans.app.openid_providers.get( openid_provider ) + elif openid_url: + openid_provider_obj = OpenIDProvider( openid_url, openid_url, openid_url ) #for manually entered links use the link for id, name and url + elif openid_provider: + message = 'Invalid OpenID provider specified: %s' % ( openid_provider ) + else: + message = 'An OpenID provider was not specified' + process_url = trans.request.base.rstrip( '/' ) + url_for( controller='user', action='openid_process', referer=referer, auto_associate=auto_associate, openid_provider=openid_provider ) + if openid_provider_obj is not None: request = None try: - request = consumer.begin( openid_url ) + request = consumer.begin( openid_provider_obj.op_endpoint_url ) if request is None: - message = 'No OpenID services are available at %s' % openid_url + message = 'No OpenID services are available at %s' % openid_provider_obj.op_endpoint_url except Exception, e: message = 'Failed to begin OpenID authentication: %s' % str( e ) if request is not None: - trans.app.openid_manager.add_sreg( trans, request, optional=[ 'nickname', 'email' ] ) + sreg_request = trans.app.openid_manager.add_sreg( trans, request, required=openid_provider_obj.sreg_required, optional=openid_provider_obj.sreg_optional ) if request.shouldSendRedirect(): redirect_url = request.redirectURL( trans.request.base, process_url ) @@ -114,6 +116,7 @@ info = consumer.complete( kwd, trans.request.url ) display_identifier = info.getDisplayIdentifier() redirect_url = kwd.get( 'referer', url_for( '/' ) ) + openid_provider = kwd.get( 'openid_provider', '' ) if info.status == trans.app.openid_manager.FAILURE and display_identifier: message = "Login via OpenID failed. The technical reason for this follows, please include this message in your email if you need to %s to resolve this problem: %s" % ( contact, info.message ) return trans.response.send_redirect( url_for( controller='user', @@ -125,6 +128,9 @@ if info.endpoint.canonicalID: display_identifier = info.endpoint.canonicalID user_openid = trans.sa_session.query( trans.app.model.UserOpenID ).filter( trans.app.model.UserOpenID.table.c.openid == display_identifier ).first() + openid_provider_obj = trans.app.openid_providers.get( openid_provider ) + if not openid_provider_obj: + openid_provider_obj = OpenIDProvider( display_identifier, display_identifier, display_identifier ) if not user_openid: user_openid = trans.app.model.UserOpenID( session=trans.galaxy_session, openid=display_identifier ) elif not user_openid.user and user_openid.session.id != trans.galaxy_session.id: @@ -132,6 +138,7 @@ elif user_openid.user and not auto_associate: trans.handle_user_login( user_openid.user, webapp ) trans.log_event( "User logged in via OpenID: %s" % display_identifier ) + openid_provider_obj.post_authentication( trans, trans.app.openid_manager, info ) trans.response.send_redirect( redirect_url ) return if auto_associate and trans.user: @@ -151,6 +158,7 @@ trans.log_event( "User associated OpenID: %s" % display_identifier ) message = "The OpenID <strong>%s</strong> has been associated with your Galaxy account, <strong>%s</strong>." % ( display_identifier, trans.user.email ) status = "done" + openid_provider_obj.post_authentication( trans, trans.app.openid_manager, info ) trans.response.send_redirect( url_for( controller='user', action='openid_manage', use_panels=True, @@ -162,15 +170,18 @@ message = "OpenID authentication was successful, but you need to associate your OpenID with a Galaxy account." sreg_resp = trans.app.openid_manager.get_sreg( info ) try: - username = sreg_resp.get( 'nickname', '' ) + sreg_username_name = openid_provider_obj.use_for.get( 'username' ) + username = sreg_resp.get( sreg_username_name, '' ) except AttributeError: username = '' try: - email = sreg_resp.get( 'email', '' ) + sreg_email_name = openid_provider_obj.use_for.get( 'email' ) + email = sreg_resp.get( sreg_email_name, '' ) except AttributeError: email = '' trans.response.send_redirect( url_for( controller='user', action='openid_associate', + openid_provider=openid_provider, use_panels=True, username=username, email=email, @@ -199,6 +210,8 @@ email = kwd.get( 'email', '' ) username = kwd.get( 'username', '' ) referer = kwd.get( 'referer', trans.request.referer ) + openid_provider = kwd.get( 'openid_provider', '' ) + openid_provider_obj = trans.app.openid_providers.get( openid_provider ) params = util.Params( kwd ) is_admin = cntrller == 'admin' and trans.user_is_admin() openids = trans.galaxy_session.openids @@ -219,8 +232,9 @@ redirect_url = referer if not redirect_url: redirect_url = url_for( '/' ) - trans.response.send_redirect( redirect_url ) - return + if openid_provider_obj: + return trans.response.send_redirect( url_for( controller='user', action='openid_auth', openid_provider=openid_provider, referer=redirect_url ) ) + return trans.response.send_redirect( redirect_url ) if kwd.get( 'create_user_button', False ): password = kwd.get( 'password', '' ) confirm = kwd.get( 'confirm', '' ) @@ -251,7 +265,9 @@ redirect_url = referer if not redirect_url: redirect_url = url_for( '/' ) - trans.response.send_redirect( redirect_url ) + if openid_provider_obj: + return trans.response.send_redirect( url_for( controller='user', action='openid_auth', openid_provider=openid_provider, referer=redirect_url ) ) + return trans.response.send_redirect( redirect_url ) else: message = error status = 'error' @@ -285,7 +301,8 @@ user_type_fd_id_select_field=user_type_fd_id_select_field, user_type_form_definition=user_type_form_definition, widgets=widgets, - openids=openids ) + openids=openids, + openid_provider=openid_provider ) @web.expose @web.require_login( 'manage OpenIDs' ) def openid_disassociate( self, trans, webapp='galaxy', **kwd ): @@ -340,7 +357,7 @@ use_panels=use_panels, id=kwd['id'] ) ) kwd['referer'] = url_for( controller='user', action='openid_manage', use_panels=True ) - kwd['openid_providers'] = OPENID_PROVIDERS + kwd['openid_providers'] = trans.app.openid_providers return self.user_openid_grid( trans, **kwd ) @web.expose def login( self, trans, webapp='galaxy', redirect_url='', refresh_frames=[], **kwd ): @@ -351,6 +368,7 @@ header = '' user = None email = kwd.get( 'email', '' ) + openid_provider = kwd.get( 'openid_provider', '' ) if kwd.get( 'login_button', False ): if webapp == 'galaxy' and not refresh_frames: if trans.app.config.require_login: @@ -385,7 +403,7 @@ refresh_frames=refresh_frames, message=message, status=status, - openid_providers=OPENID_PROVIDERS, + openid_providers=trans.app.openid_providers, active_view="user" ) def __validate_login( self, trans, webapp='galaxy', **kwd ): message = kwd.get( 'message', '' ) diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 lib/galaxy/web/framework/openid_manager.py --- a/lib/galaxy/web/framework/openid_manager.py +++ b/lib/galaxy/web/framework/openid_manager.py @@ -1,5 +1,5 @@ """ -Mange the OpenID consumer and related data stores. +Manage the OpenID consumer and related data stores. """ import os, pickle, logging diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 openid/aol.xml --- /dev/null +++ b/openid/aol.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<provider id="aol" name="AOL/AIM"> + <op_endpoint_url>http://openid.aol.com</op_endpoint_url> +</provider> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 openid/genomespace.xml --- /dev/null +++ b/openid/genomespace.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<provider id="genomespace" name="GenomeSpace"> + <op_endpoint_url>https://identity.genomespace.org/identityServer/xrd.jsp</op_endpoint_url> + <sreg> + <field name="nickname" required="True"> + <use_for name="username"/> + <store_user_preference name="genomespace_username"/> + </field> + <field name="email" required="False"> + <use_for name="email"/> + </field> + <field name="gender" required="True"> + <store_user_preference name="genomespace_token"/> + </field> + </sreg> +</provider> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 openid/google.xml --- /dev/null +++ b/openid/google.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<provider id="google" name="Google"> + <op_endpoint_url>https://www.google.com/accounts/o8/id</op_endpoint_url> +</provider> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 openid/launchpad.xml --- /dev/null +++ b/openid/launchpad.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<provider id="launchpad" name="Launchpad"> + <op_endpoint_url>http://login.launchpad.net</op_endpoint_url> +</provider> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 openid/yahoo.xml --- /dev/null +++ b/openid/yahoo.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<provider id="yahoo" name="Yahoo!"> + <op_endpoint_url>http://yahoo.com</op_endpoint_url> +</provider> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 openid_conf.xml.sample --- /dev/null +++ b/openid_conf.xml.sample @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<openid> + <provider file="google.xml" /> + <provider file="yahoo.xml" /> + <provider file="aol.xml" /> + <provider file="launchpad.xml" /> + <provider file="genomespace.xml" /> +</openid> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 run.sh --- a/run.sh +++ b/run.sh @@ -15,6 +15,7 @@ tool_conf.xml.sample tool_data_table_conf.xml.sample tool_sheds_conf.xml.sample + openid_conf.xml.sample universe_wsgi.ini.sample tool-data/shared/ucsc/builds.txt.sample tool-data/shared/igv/igv_build_sites.txt.sample diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 templates/user/login.mako --- a/templates/user/login.mako +++ b/templates/user/login.mako @@ -59,7 +59,7 @@ </%def> -<%def name="render_login_form( form_action=None )"> +<%def name="render_login_form( form_action=None, openid_provider='' )"><% if form_action is None: @@ -77,6 +77,7 @@ <input type="text" name="email" value="${email}" size="40"/><input type="hidden" name="webapp" value="${webapp}" size="40"/><input type="hidden" name="referer" value="${referer}" size="40"/> + <input type="hidden" name="openid_provider" value="${openid_provider}" /></div><div class="form-row"><label>Password:</label> @@ -94,10 +95,9 @@ </%def><%def name="render_openid_form( referer, auto_associate, openid_providers )"> - <div class="toolForm"><div class="toolFormTitle">OpenID Login</div> - <form name="openid" id="openid" action="${h.url_for( controller='user', action='openid_auth' )}" method="post" > + <form name="openid" id="openid" action="${h.url_for( controller='user', action='openid_auth' )}" method="post" target="_parent" ><div class="form-row"><label>OpenID URL:</label><input type="text" name="openid_url" size="60" style="background-image:url('${h.url_for( '/static/images/openid-16x16.gif' )}' ); background-repeat: no-repeat; padding-right: 20px; background-position: 99% 50%;"/> @@ -107,8 +107,8 @@ </div><div class="form-row"> Or, authenticate with your <select name="openid_provider"> - %for provider in openid_providers.keys(): - <option>${provider}</option> + %for provider in openid_providers: + <option value="${provider.id}">${provider.name}</option> %endfor </select> account. </div> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 templates/user/openid_associate.mako --- a/templates/user/openid_associate.mako +++ b/templates/user/openid_associate.mako @@ -65,11 +65,11 @@ <% form_action = h.url_for( cntrller=cntrller, use_panels=use_panels ) %> - ${render_login_form( form_action=form_action )} + ${render_login_form( form_action=form_action, openid_provider=openid_provider )} <br/> - ${render_registration_form( form_action=form_action )} + ${render_registration_form( form_action=form_action, openid_provider=openid_provider )} </div></div> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 templates/user/register.mako --- a/templates/user/register.mako +++ b/templates/user/register.mako @@ -21,7 +21,7 @@ ${render_registration_form()} %endif -<%def name="render_registration_form( form_action=None )"> +<%def name="render_registration_form( form_action=None, openid_provider='' )"><% if form_action is None: @@ -38,6 +38,7 @@ <input type="text" name="email" value="${email}" size="40"/><input type="hidden" name="webapp" value="${webapp}" size="40"/><input type="hidden" name="referer" value="${referer}" size="40"/> + <input type="hidden" name="openid_provider" value="${openid_provider}" /></div><div class="form-row"><label>Password:</label> diff -r 463fc935b97cf697f0fb0a48aae052a35c8e95c7 -r b9e188c4ecba66b5e065ac88b6b9d301a4879156 universe_wsgi.ini.sample --- a/universe_wsgi.ini.sample +++ b/universe_wsgi.ini.sample @@ -495,6 +495,7 @@ # Enable authentication via OpenID. Allows users to log in to their Galaxy # account by authenticating with an OpenID provider. #enable_openid = False +#openid_config_file = openid_conf.xml # Enable the (experimental! beta!) Web API. Documentation forthcoming. #enable_api = False Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.