mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-25 10:24:38 +08:00
Merge pull request #13281 from mvdbeek/drop_openid_post_authentication_protocols
This commit is contained in:
@@ -3701,28 +3701,6 @@
|
||||
:Type: str
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~
|
||||
``enable_openid``
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
:Description:
|
||||
Enable access to post-authentication options via OpenID.
|
||||
:Default: ``false``
|
||||
:Type: bool
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``openid_consumer_cache_path``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:Description:
|
||||
If OpenID is enabled, consumer cache directory to use.
|
||||
The value of this option will be resolved with respect to
|
||||
<cache_dir>.
|
||||
:Default: ``openid_consumer_cache``
|
||||
:Type: str
|
||||
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
``enable_tool_tags``
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -4823,13 +4801,17 @@
|
||||
:Default: ``plugins/welcome_page/new_user/static/topics/``
|
||||
:Type: str
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
``vault_config_file``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:Description:
|
||||
Vault configuration
|
||||
Vault config file.
|
||||
The value of this option will be resolved with respect to
|
||||
<config_dir>.
|
||||
:Default: ``vault_conf.yml``
|
||||
:Type: str
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -313,6 +313,8 @@ OPTION_ACTIONS = {
|
||||
'communication_server_port': _DeprecatedAndDroppedAction(),
|
||||
'persistent_communication_rooms': _DeprecatedAndDroppedAction(),
|
||||
'legacy_eager_objectstore_initialization': _DeprecatedAndDroppedAction(),
|
||||
'enable_openid': _DeprecatedAndDroppedAction(),
|
||||
'openid_consumer_cache_path': _DeprecatedAndDroppedAction(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1811,14 +1811,6 @@ galaxy:
|
||||
# production server.
|
||||
#master_api_key: null
|
||||
|
||||
# Enable access to post-authentication options via OpenID.
|
||||
#enable_openid: false
|
||||
|
||||
# If OpenID is enabled, consumer cache directory to use.
|
||||
# The value of this option will be resolved with respect to
|
||||
# <cache_dir>.
|
||||
#openid_consumer_cache_path: openid_consumer_cache
|
||||
|
||||
# Enable tool tags (associating tools with tags). This has its own
|
||||
# option since its implementation has a few performance implications
|
||||
# on startup for large servers.
|
||||
@@ -2353,7 +2345,8 @@ galaxy:
|
||||
# is relative to galaxy/static
|
||||
#welcome_directory: plugins/welcome_page/new_user/static/topics/
|
||||
|
||||
# Vault config file
|
||||
# Vault config file.
|
||||
# The value of this option will be resolved with respect to
|
||||
# <config_dir>.
|
||||
vault_config_file: vault_conf.yml
|
||||
#vault_config_file: vault_conf.yml
|
||||
|
||||
|
||||
@@ -2701,21 +2701,6 @@ mapping:
|
||||
a real admin user account via API.
|
||||
You should probably not set this on a production server.
|
||||
|
||||
enable_openid:
|
||||
type: bool
|
||||
default: false
|
||||
required: false
|
||||
desc: |
|
||||
Enable access to post-authentication options via OpenID.
|
||||
|
||||
openid_consumer_cache_path:
|
||||
type: str
|
||||
default: openid_consumer_cache
|
||||
path_resolves_to: cache_dir
|
||||
required: false
|
||||
desc: |
|
||||
If OpenID is enabled, consumer cache directory to use.
|
||||
|
||||
enable_tool_tags:
|
||||
type: bool
|
||||
default: false
|
||||
|
||||
@@ -147,7 +147,6 @@ class ConfigSerializer(base.ModelSerializer):
|
||||
'helpsite_url': _use_config,
|
||||
'lims_doc_url': _defaults_to("https://usegalaxy.org/u/rkchak/p/sts"),
|
||||
'default_locale': _use_config,
|
||||
'enable_openid': _use_config,
|
||||
'enable_tool_recommendations': _use_config,
|
||||
'enable_account_interface': _use_config,
|
||||
'tool_recommendation_model_path': _use_config,
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
OpenID functionality
|
||||
"""
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<openid>
|
||||
</openid>
|
||||
@@ -1,75 +0,0 @@
|
||||
"""
|
||||
Manage the OpenID consumer and related data stores.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
|
||||
try:
|
||||
from openid import oidutil
|
||||
from openid.consumer import consumer
|
||||
from openid.extensions import sreg
|
||||
from openid.store import filestore
|
||||
except ImportError:
|
||||
oidutil = None
|
||||
|
||||
class FakeConsumer:
|
||||
def __getattr__(x, y):
|
||||
return None
|
||||
consumer = FakeConsumer()
|
||||
|
||||
|
||||
OPENID_IMPORT_MESSAGE = ('The Python openid package is required to use this '
|
||||
'feature, please install it')
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def oidlog(message, level=0):
|
||||
log.debug(message)
|
||||
|
||||
|
||||
if oidutil is not None:
|
||||
oidutil.log = oidlog
|
||||
|
||||
|
||||
class OpenIDManager:
|
||||
def __init__(self, cache_path):
|
||||
assert oidutil is not None, OPENID_IMPORT_MESSAGE
|
||||
self.session_path = os.path.join(cache_path, 'session')
|
||||
self.store_path = os.path.join(cache_path, 'store')
|
||||
for dir in self.session_path, self.store_path:
|
||||
if not os.path.exists(dir):
|
||||
os.makedirs(dir)
|
||||
self.store = filestore.FileOpenIDStore(self.store_path)
|
||||
|
||||
def get_session(self, trans):
|
||||
session_file = os.path.join(self.session_path, str(trans.galaxy_session.id))
|
||||
if not os.path.exists(session_file):
|
||||
pickle.dump(dict(), open(session_file, 'wb'))
|
||||
return pickle.load(open(session_file, 'rb'))
|
||||
|
||||
def persist_session(self, trans, oidconsumer):
|
||||
session_file = os.path.join(self.session_path, str(trans.galaxy_session.id))
|
||||
pickle.dump(oidconsumer.session, open(session_file, 'wb'))
|
||||
|
||||
def get_consumer(self, trans):
|
||||
return consumer.Consumer(self.get_session(trans), self.store)
|
||||
|
||||
def add_sreg(self, trans, request, required=None, optional=None):
|
||||
if required is None:
|
||||
required = []
|
||||
if optional is None:
|
||||
optional = []
|
||||
sreg_request = sreg.SRegRequest(required=required, optional=optional)
|
||||
request.addExtension(sreg_request)
|
||||
|
||||
def get_sreg(self, info):
|
||||
return sreg.SRegResponse.fromSuccessResponse(info)
|
||||
|
||||
# so I don't have to expose all of openid.consumer.consumer
|
||||
FAILURE = consumer.FAILURE
|
||||
SUCCESS = consumer.SUCCESS
|
||||
CANCEL = consumer.CANCEL
|
||||
SETUP_NEEDED = consumer.SETUP_NEEDED
|
||||
@@ -1,144 +0,0 @@
|
||||
"""
|
||||
Contains OpenID provider functionality
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
from galaxy.util import parse_xml, string_as_bool
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
NO_PROVIDER_ID = 'None'
|
||||
RESERVED_PROVIDER_IDS = [NO_PROVIDER_ID]
|
||||
|
||||
|
||||
class OpenIDProvider:
|
||||
'''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
|
||||
never_associate_with_user = string_as_bool(provider_elem.get('never_associate_with_user', 'False'))
|
||||
assert (provider_id and provider_name and op_endpoint_url), Exception("OpenID Provider improperly configured")
|
||||
assert provider_id not in RESERVED_PROVIDER_IDS, Exception(f'Specified OpenID Provider uses a reserved id: {provider_id}')
|
||||
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_required, sreg_optional=sreg_optional, use_for=use_for, store_user_preference=store_user_preference, never_associate_with_user=never_associate_with_user)
|
||||
|
||||
def __init__(self, id, name, op_endpoint_url, sreg_required=None, sreg_optional=None, use_for=None, store_user_preference=None, never_associate_with_user=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 = {}
|
||||
if never_associate_with_user:
|
||||
self.never_associate_with_user = True
|
||||
else:
|
||||
self.never_associate_with_user = False
|
||||
|
||||
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.items():
|
||||
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)
|
||||
else:
|
||||
raise Exception('Only sreg is currently supported.')
|
||||
trans.sa_session.add(trans.user)
|
||||
trans.sa_session.flush()
|
||||
|
||||
def has_post_authentication_actions(self):
|
||||
return bool(self.store_user_preference)
|
||||
|
||||
|
||||
class OpenIDProviders:
|
||||
'''Collection of OpenID Providers'''
|
||||
NO_PROVIDER_ID = NO_PROVIDER_ID
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, filename):
|
||||
try:
|
||||
return cls.from_elem(parse_xml(filename).getroot())
|
||||
except Exception as e:
|
||||
log.error(f'Failed to load OpenID Providers: {e}')
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_elem(cls, xml_root):
|
||||
oid_elem = xml_root
|
||||
providers = {}
|
||||
for elem in oid_elem.findall('provider'):
|
||||
try:
|
||||
provider = OpenIDProvider.from_file(os.path.join('lib/galaxy/openid', elem.get('file')))
|
||||
providers[provider.id] = provider
|
||||
log.debug(f'Loaded OpenID provider: {provider.name} ({provider.id})')
|
||||
except Exception as e:
|
||||
log.error(f'Failed to add OpenID provider: {e}')
|
||||
return cls(providers)
|
||||
|
||||
def __init__(self, providers=None):
|
||||
if providers:
|
||||
self.providers = providers
|
||||
else:
|
||||
self.providers = {}
|
||||
self._banned_identifiers = [provider.op_endpoint_url for provider in self.providers.values() if provider.never_associate_with_user]
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.providers.values()
|
||||
|
||||
def get(self, name, default=None):
|
||||
if name in self.providers:
|
||||
return self.providers[name]
|
||||
else:
|
||||
return default
|
||||
|
||||
def new_provider_from_identifier(self, identifier):
|
||||
return OpenIDProvider(None, identifier, identifier, never_associate_with_user=identifier in self._banned_identifiers)
|
||||
@@ -1,74 +0,0 @@
|
||||
"""
|
||||
Contains the OpenID interface in the Universe class
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from galaxy import web
|
||||
from galaxy.openid.openid_manager import OpenIDManager
|
||||
from galaxy.openid.providers import OpenIDProviders
|
||||
from galaxy.structured_app import StructuredApp
|
||||
from galaxy.util import unicodify
|
||||
from galaxy.web import url_for
|
||||
from galaxy.webapps.base.controller import BaseUIController
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OpenID(BaseUIController):
|
||||
|
||||
def __init__(self, app: StructuredApp):
|
||||
super().__init__(app)
|
||||
if app.config.enable_openid:
|
||||
self.openid_manager = OpenIDManager(app.config.openid_consumer_cache_path)
|
||||
self.openid_providers = OpenIDProviders.from_file('lib/galaxy/openid/openid_conf.xml')
|
||||
|
||||
@web.expose
|
||||
def openid_auth(self, trans, **kwd):
|
||||
'''Handles user request to access an OpenID provider'''
|
||||
if not trans.app.config.enable_openid:
|
||||
return trans.show_error_message("OpenID authentication is not enabled in this instance of Galaxy.")
|
||||
consumer = self.openid_manager.get_consumer(trans)
|
||||
openid_provider = kwd.get('openid_provider')
|
||||
if openid_provider:
|
||||
openid_provider_obj = self.openid_providers.get(openid_provider)
|
||||
else:
|
||||
return trans.show_error_message("An OpenID provider was not specified.")
|
||||
if not openid_provider_obj:
|
||||
return trans.show_error_message("An OpenID provider is invalid.")
|
||||
process_url = trans.request.base.rstrip('/') + url_for(controller='openid', action='openid_process', openid_provider=openid_provider)
|
||||
request = None
|
||||
try:
|
||||
request = consumer.begin(openid_provider_obj.op_endpoint_url)
|
||||
if request is None:
|
||||
return trans.show_error_message(f"No OpenID services are available at {openid_provider_obj.op_endpoint_url}.")
|
||||
except Exception as e:
|
||||
return trans.show_error_message(f"Failed to begin OpenID authentication: {unicodify(e)}.")
|
||||
if request is not None:
|
||||
self.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)
|
||||
self.openid_manager.persist_session(trans, consumer)
|
||||
return trans.response.send_redirect(redirect_url)
|
||||
else:
|
||||
form = request.htmlMarkup(trans.request.base, process_url, form_tag_attrs={'id': 'openid_message', 'target': '_top'})
|
||||
self.openid_manager.persist_session(trans, consumer)
|
||||
return form
|
||||
return trans.show_error_message("OpenID request failed.")
|
||||
|
||||
@web.expose
|
||||
def openid_process(self, trans, **kwd):
|
||||
'''Handle's response from OpenID Providers'''
|
||||
return_link = f"Click <a href='{url_for('/')}'>here</a> to return."
|
||||
if not trans.app.config.enable_openid:
|
||||
return trans.show_error_message(f"OpenID authentication is not enabled in this instance of Galaxy. {return_link}")
|
||||
consumer = self.openid_manager.get_consumer(trans)
|
||||
info = consumer.complete(kwd, trans.request.url)
|
||||
openid_provider = kwd.get('openid_provider', None)
|
||||
if info.status == self.openid_manager.SUCCESS:
|
||||
openid_provider_obj = self.openid_providers.get(openid_provider)
|
||||
openid_provider_obj.post_authentication(trans, self.openid_manager, info)
|
||||
return trans.show_message(f"Processed OpenID authentication. {return_link}")
|
||||
else:
|
||||
return trans.show_error_message(f"Authentication via OpenID failed: {info.message}. {return_link}")
|
||||
@@ -1 +0,0 @@
|
||||
../../../lib/galaxy/openid
|
||||
@@ -49,7 +49,6 @@ PACKAGES = [
|
||||
'galaxy.jobs.runners.util.job_script',
|
||||
'galaxy.jobs.splitters',
|
||||
'galaxy.managers',
|
||||
'galaxy.openid',
|
||||
'galaxy.schema',
|
||||
'galaxy.tools',
|
||||
'galaxy.tools.actions',
|
||||
|
||||
@@ -81,7 +81,6 @@ PATH_CONFIG_PROPERTIES = [
|
||||
'object_store_config_file',
|
||||
'oidc_backends_config_file',
|
||||
'oidc_config_file',
|
||||
'openid_consumer_cache_path',
|
||||
'sanitize_allowlist_file',
|
||||
'shed_data_manager_config_file',
|
||||
'shed_tool_config_file',
|
||||
|
||||
@@ -139,7 +139,6 @@ class ExpectedValues:
|
||||
'object_store_config_file': self._in_config_dir('object_store_conf.xml'),
|
||||
'oidc_backends_config_file': self._in_config_dir('oidc_backends_config.xml'),
|
||||
'oidc_config_file': self._in_config_dir('oidc_config.xml'),
|
||||
'openid_consumer_cache_path': self._in_cache_dir('openid_consumer_cache'),
|
||||
'sanitize_allowlist_file': self._in_managed_config_dir('sanitize_allowlist.txt'),
|
||||
'shed_data_manager_config_file': self._in_managed_config_dir('shed_data_manager_conf.xml'),
|
||||
'shed_tool_config_file': self._in_managed_config_dir('shed_tool_conf.xml'),
|
||||
|
||||
Reference in New Issue
Block a user