Skip to content

Commit

Permalink
Abstract OpenID providers to be defined outside of the Python code an…
Browse files Browse the repository at this point in the history
…d 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.
  • Loading branch information
blankenberg committed Mar 23, 2012
1 parent c0c286a commit eed27b8
Show file tree
Hide file tree
Showing 17 changed files with 218 additions and 30 deletions.
4 changes: 4 additions & 0 deletions lib/galaxy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down Expand Up @@ -105,6 +106,9 @@ def __init__( self, **kwargs ):
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
Expand Down
2 changes: 2 additions & 0 deletions lib/galaxy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ def __init__( self, **kwargs ):
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 ) )
Expand Down
3 changes: 3 additions & 0 deletions lib/galaxy/openid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
OpenID functionality
"""
118 changes: 118 additions & 0 deletions lib/galaxy/openid/providers.py
Original file line number Diff line number Diff line change
@@ -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
60 changes: 39 additions & 21 deletions lib/galaxy/web/controllers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__ )

Expand All @@ -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"
Expand Down Expand Up @@ -68,19 +63,26 @@ def openid_auth( self, trans, webapp='galaxy', **kwd ):
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 )
Expand Down Expand Up @@ -114,6 +116,7 @@ def openid_process( self, trans, webapp='galaxy', **kwd ):
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',
Expand All @@ -125,13 +128,17 @@ def openid_process( self, trans, webapp='galaxy', **kwd ):
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:
user_openid.session = trans.galaxy_session
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:
Expand All @@ -151,6 +158,7 @@ def openid_process( self, trans, webapp='galaxy', **kwd ):
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,
Expand All @@ -162,15 +170,18 @@ def openid_process( self, trans, webapp='galaxy', **kwd ):
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,
Expand Down Expand Up @@ -199,6 +210,8 @@ def openid_associate( self, trans, cntrller='user', webapp='galaxy', **kwd ):
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
Expand All @@ -219,8 +232,9 @@ def openid_associate( self, trans, cntrller='user', webapp='galaxy', **kwd ):
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', '' )
Expand Down Expand Up @@ -251,7 +265,9 @@ def openid_associate( self, trans, cntrller='user', webapp='galaxy', **kwd ):
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'
Expand Down Expand Up @@ -285,7 +301,8 @@ def openid_associate( self, trans, cntrller='user', webapp='galaxy', **kwd ):
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 ):
Expand Down Expand Up @@ -340,7 +357,7 @@ def openid_manage( self, trans, webapp='galaxy', **kwd ):
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 ):
Expand All @@ -351,6 +368,7 @@ def login( self, trans, webapp='galaxy', redirect_url='', refresh_frames=[], **k
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:
Expand Down Expand Up @@ -385,7 +403,7 @@ def login( self, trans, webapp='galaxy', redirect_url='', refresh_frames=[], **k
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', '' )
Expand Down
2 changes: 1 addition & 1 deletion lib/galaxy/web/framework/openid_manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Mange the OpenID consumer and related data stores.
Manage the OpenID consumer and related data stores.
"""

import os, pickle, logging
Expand Down
4 changes: 4 additions & 0 deletions openid/aol.xml
Original file line number Diff line number Diff line change
@@ -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>
16 changes: 16 additions & 0 deletions openid/genomespace.xml
Original file line number Diff line number Diff line change
@@ -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>
4 changes: 4 additions & 0 deletions openid/google.xml
Original file line number Diff line number Diff line change
@@ -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>
4 changes: 4 additions & 0 deletions openid/launchpad.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<provider id="launchpad" name="Launchpad">
<op_endpoint_url>http://login.launchpad.net</op_endpoint_url>
</provider>
4 changes: 4 additions & 0 deletions openid/yahoo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<provider id="yahoo" name="Yahoo!">
<op_endpoint_url>http://yahoo.com</op_endpoint_url>
</provider>
8 changes: 8 additions & 0 deletions openid_conf.xml.sample
Original file line number Diff line number Diff line change
@@ -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>
1 change: 1 addition & 0 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ SAMPLES="
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
Expand Down
Loading

0 comments on commit eed27b8

Please sign in to comment.