Replace api_inherit_admin with BootstrapAdminUser

This commit is contained in:
mvdbeek
2020-12-22 11:18:31 +01:00
parent 34a30a27a0
commit 298bf54afc
11 changed files with 40 additions and 33 deletions
+1 -1
View File
@@ -422,7 +422,7 @@ class CommonConfigurationMixin:
def is_admin_user(self, user):
"""Determine if the provided user is listed in `admin_users`."""
return user is not None and user.email in self.admin_users_list
return user and (user.email in self.admin_users_list or user.bootstrap_admin_user)
@property
def sentry_dsn_public(self):
+4 -4
View File
@@ -103,13 +103,13 @@ class ProvidesUserContext:
""" For transaction-like objects to provide Galaxy convience layer for
reasoning about users.
Mixed in class must provide `user`, `api_inherit_admin`, and `app`
Mixed in class must provide `user` and `app`
properties.
"""
@property
def anonymous(self):
return self.user is None and not self.api_inherit_admin
return self.user is None
def get_current_user_roles(self):
user = self.user
@@ -121,7 +121,7 @@ class ProvidesUserContext:
@property
def user_is_admin(self):
return self.api_inherit_admin or self.app.config.is_admin_user(self.user)
return self.app.config.is_admin_user(self.user)
@property
def user_can_do_run_as(self):
@@ -130,7 +130,7 @@ class ProvidesUserContext:
return False
user_in_run_as_users = self.user and self.user.email in run_as_users
# Can do if explicitly in list or master_api_key supplied.
can_do_run_as = user_in_run_as_users or self.api_inherit_admin
can_do_run_as = user_in_run_as_users or self.user.bootstrap_admin_user
return can_do_run_as
@property
+14 -1
View File
@@ -1,6 +1,7 @@
"""
Manager and Serializer for Users.
"""
import hashlib
import logging
import random
import socket
@@ -14,7 +15,8 @@ from sqlalchemy.orm.exc import NoResultFound
from galaxy import (
exceptions,
model,
util
schema,
util,
)
from galaxy.managers import (
api_keys,
@@ -239,6 +241,8 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
try:
provided_key = sa_session.query(self.app.model.APIKeys).filter(self.app.model.APIKeys.table.c.key == api_key).one()
except NoResultFound:
if self.check_master_api_key(api_key=api_key):
return schema.BoostrapAdminUser()
raise exceptions.AuthenticationFailed('Provided API key is not valid.')
if provided_key.user.deleted:
raise exceptions.AuthenticationFailed('User account is deactivated, please contact an administrator.')
@@ -247,6 +251,15 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
raise exceptions.AuthenticationFailed('Provided API key has expired.')
return provided_key.user
def check_master_api_key(self, api_key):
master_api_key = getattr(self.app.config, 'master_api_key', None)
if not master_api_key:
return False
# Hash keys to make them the same size, so we can do safe comparison.
master_hash = hashlib.sha256(util.smart_str(master_api_key)).hexdigest()
provided_hash = hashlib.sha256(util.smart_str(api_key)).hexdigest()
return util.safe_str_cmp(master_hash, provided_hash)
# ---- admin
def is_admin(self, user, trans=None):
"""Return True if this user is an admin (or session is authenticated as admin).
+1
View File
@@ -356,6 +356,7 @@ class JobLike:
class User(Dictifiable, RepresentById):
use_pbkdf2 = True
bootstrap_admin_user = False
"""
Data for a Galaxy user or admin and relations to their
histories, credentials, and roles.
+12
View File
@@ -0,0 +1,12 @@
import typing
from pydantic import BaseModel
class BoostrapAdminUser(BaseModel):
id = 0
email: typing.Optional[str] = None
bootstrap_admin_user = True
def all_roles(*args) -> list:
return []
-2
View File
@@ -161,7 +161,6 @@ def legacy_expose_api(func, to_json=True, user_required=True):
return "Malformed user id ( %s ) specified, unable to decode." % str(kwargs['payload']['run_as'])
try:
user = trans.sa_session.query(trans.app.model.User).get(decoded_user_id)
trans.api_inherit_admin = trans.user_is_admin
trans.set_user(user)
except Exception:
trans.response.status = 400
@@ -286,7 +285,6 @@ def expose_api(func, to_json=True, user_required=True, user_or_session_required=
return __api_error_response(trans, err_code=error_code, err_msg=error_message, status_code=400)
try:
user = trans.sa_session.query(trans.app.model.User).get(decoded_user_id)
trans.api_inherit_admin = trans.user_is_admin
trans.set_user(user)
except Exception:
error_code = error_codes.USER_INVALID_RUN_AS
+5 -23
View File
@@ -1,7 +1,6 @@
"""
"""
import datetime
import hashlib
import inspect
import logging
import os
@@ -33,8 +32,6 @@ from galaxy.managers.users import UserManager
from galaxy.util import (
asbool,
safe_makedirs,
safe_str_cmp,
smart_str,
unicodify
)
from galaxy.util.sanitize_html import sanitize_html
@@ -204,8 +201,6 @@ class GalaxyWebTransaction(base.DefaultWebTransaction,
# that the current history should not be used for parameter values
# and such).
self.workflow_building_mode = False
# Flag indicating whether this is an API call and the API key user is an administrator
self.api_inherit_admin = False
self.__user = None
self.galaxy_session = None
self.error_message = None
@@ -362,9 +357,10 @@ class GalaxyWebTransaction(base.DefaultWebTransaction,
def set_user(self, user):
"""Set the current user."""
if self.galaxy_session:
self.galaxy_session.user = user
self.sa_session.add(self.galaxy_session)
self.sa_session.flush()
if user.bootstrap_admin_user:
self.galaxy_session.user = user
self.sa_session.add(self.galaxy_session)
self.sa_session.flush()
self.__user = user
user = property(get_user, set_user)
@@ -407,12 +403,7 @@ class GalaxyWebTransaction(base.DefaultWebTransaction,
api_key = self.request.params.get('key', None) or self.request.headers.get('x-api-key', None)
secure_id = self.get_cookie(name=session_cookie)
api_key_supplied = self.environ.get('is_api_request', False) and api_key
if api_key_supplied and self._check_master_api_key(api_key):
self.api_inherit_admin = True
log.info("Session authenticated using Galaxy master api key")
self.user = None
self.galaxy_session = None
elif api_key_supplied:
if api_key_supplied:
# Sessionless API transaction, we just need to associate a user.
try:
user = self.user_manager.by_api_key(api_key)
@@ -434,15 +425,6 @@ class GalaxyWebTransaction(base.DefaultWebTransaction,
self.user = None
self.galaxy_session = None
def _check_master_api_key(self, api_key):
master_api_key = getattr(self.app.config, 'master_api_key', None)
if not master_api_key:
return False
# Hash keys to make them the same size, so we can do safe comparison.
master_hash = hashlib.sha256(smart_str(master_api_key)).hexdigest()
provided_hash = hashlib.sha256(smart_str(api_key)).hexdigest()
return safe_str_cmp(master_hash, provided_hash)
def _ensure_valid_session(self, session_cookie, create=True):
"""
Ensure that a valid Galaxy session exists and is available as
+1 -1
View File
@@ -222,7 +222,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesApiKeysMixin, B
# only allow updating other users if they're admin
editing_someone_else = current_user != user_to_update
is_admin = trans.api_inherit_admin or self.user_manager.is_admin(current_user)
is_admin = self.user_manager.is_admin(current_user)
if editing_someone_else and not is_admin:
raise exceptions.InsufficientPermissionsException('You are not allowed to update that user', id=id)
-1
View File
@@ -24,7 +24,6 @@ class WorkRequestContext(ProvidesAppContext, ProvidesUserContext, ProvidesHistor
self.__user = user
self.__user_current_roles = None
self.__history = history
self.api_inherit_admin = False
self.workflow_building_mode = workflow_building_mode
def get_history(self, create=False):
+1
View File
@@ -33,6 +33,7 @@ class APIKeys:
class User(Dictifiable):
dict_collection_visible_keys = ['id', 'username']
dict_element_visible_keys = ['id', 'username']
bootstrap_admin_user = False
def __init__(self, email=None, password=None):
self.email = email
+1
View File
@@ -211,6 +211,7 @@ class MockTrans:
self.error_message = None
self.anonymous = False
self.debug = True
self.user_is_admin = False
self.galaxy_session = None
self.__user = user