Skip to content

Commit

Permalink
flake8
Browse files Browse the repository at this point in the history
  • Loading branch information
datamel committed Jul 2, 2021
1 parent 8e2d92d commit 3541eca
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 110 deletions.
121 changes: 55 additions & 66 deletions cylc/uiserver/authorise.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
import grp
from typing import List, Dict, Union
from graphql.execution.base import ResolveInfo
from graphql.utils.ast_to_dict import ast_to_dict
from inspect import iscoroutinefunction
import logging
import os
from tornado import web
from traitlets.traitlets import Bool

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -239,12 +239,22 @@ def get_permitted_operations(self, access_user: str):
user_conf_permitted_ops)
return allowed_operations

def is_permitted(self, access_user, operation):
def is_permitted(
self, access_user: Union[str, Dict], operation: str) -> Bool:
"""Checks if user is permitted to action operation.
Args:
access_user: User attempting to action given operation.
operation: operation name
Returns:
True if access_user permitted to action operation, otherwise,
False.
"""
if isinstance(access_user, Dict):
access_user = access_user['name']
if access_user == self.owner_user_info['user']:
print("melly in da house")
# return True
return True
logger.info(f'{access_user}: requested {operation} to ')
if str(operation) in self.get_permitted_operations(access_user):
logger.info(f'{access_user}: authorised to {operation}')
Expand All @@ -259,20 +269,19 @@ def set_owner_site_auth_conf(self):
Args:
owner_user_info: Dictionary containing information about
the ui-server owner Defaults to None.
site_config: Defaults to None....
: Optional[
Dict[str: Dict[
str: Dict[
str: Union[List[str], None]
]]]]
site_config: Defaults to None.
"""
owner_dict = {}
if not self.site_auth_config.to_dict():
# no site auth - return empty dict
self.owner_dict = owner_dict
return
# concerned with process site config
self.owner_user_info['user_groups'] = ([
f'{Authorization.GROUP_IDENTIFIER}{group}'
for group in self.owner_user_info[
'user_groups']
])
owner_dict = {}
items_to_check = [
'*', self.owner_user_info['user']]
items_to_check.extend(self.owner_user_info['user_groups'])
Expand Down Expand Up @@ -319,14 +328,6 @@ def set_owner_site_auth_conf(self):
# Now we have a reduced site auth dictionary for the current owner
self.owner_dict = owner_dict

def process_site_conf_for_owner(self, owner_dict, existing_user_conf):
""" Processes duplicate user access entries of site config of owner.
"""
print(owner_dict)
print(existing_user_conf)

pass

def expand_and_process_access_groups(self, permission_set: set) -> set:
"""Process a permission set.
Expand Down Expand Up @@ -398,9 +399,8 @@ def return_site_auth_defaults_for_access_user(
defaults.discard('')
return defaults

# GraphQL middleware


# GraphQL middleware
class AuthorizationMiddleware:

auth = None
Expand All @@ -410,58 +410,47 @@ class AuthorizationMiddleware:
READ_AUTH_OPS = {'query', 'subscription'}

def resolve(self, next_, root, info, **args):
try:
authorised = False
# # We won't be re-checking auth for return variables
# # TODO confirm GraphQL mutations won't be nested
if len(info.path) > 1:
return next_(root, info, **args)
# Check user is allowed to READ
if (info.operation.operation in self.READ_AUTH_OPS and
self.auth.is_permitted(self.current_user, 'read')):
authorised = False
# # We won't be re-checking auth for return variables
# # TODO confirm GraphQL mutations won't be nested
if len(info.path) > 1:
return next_(root, info, **args)
# Check user is allowed to READ
if (info.operation.operation in self.READ_AUTH_OPS and
self.auth.is_permitted(self.current_user, 'read')):
authorised = True
else:
# Check it is a mutation in our schema
if (isinstance(info, ResolveInfo) and info.field_name and
info.field_name in Authorization.ALL):
op_name = info.field_name
elif info.operation.operation in Authorization.ALL:
op_name = info.operation.operation

if (info.parent_type.name.lower() in
'uismutations' and op_name and
self.auth.is_permitted(
self.current_user, op_name)):
authorised = True
else:
# Check it is a mutation in our schema
if (isinstance(info, ResolveInfo) and info.field_name and
info.field_name in Authorization.ALL):
op_name = info.field_name
elif info.operation.operation in Authorization.ALL:
op_name = info.operation.operation

if (info.parent_type.name.lower() in
'uismutations' and op_name and
self.auth.is_permitted(
self.current_user, op_name)):
authorised = True

if not authorised:
logger.warn(
f"Authorisation failed for {self.current_user}"
f"{self.current_user} requested to "
f"{info.operation.operation}"
)
raise web.HTTPError(403)

if (
info.operation.operation in self.ASYNC_OPS
or iscoroutinefunction(next_)
):
return self.async_resolve(next_, root, info, **args)
return next_(root, info, **args)
except Exception as exc:
print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!{exc}')
if not authorised:
logger.warn(
f"Authorisation failed for {self.current_user}"
f":requested to {op_name}"
)
raise web.HTTPError(403)

if (
info.operation.operation in self.ASYNC_OPS
or iscoroutinefunction(next_)
):
return self.async_resolve(next_, root, info, **args)
return next_(root, info, **args)

async def async_resolve(self, next_, root, info, **args):
"""Return awaited coroutine"""
return await next_(root, info, **args)

@staticmethod
def op_names(ast):
node = ast_to_dict(ast)
for leaf in node['selections']:
if leaf['kind'] == 'Field':
yield leaf['name']['value']


def get_groups(username: str) -> List:
"""Return list of system groups for given user
Expand Down
20 changes: 10 additions & 10 deletions cylc/uiserver/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
# base configuration - always used
DEFAULT_CONF_PATH: Path = Path(uis_pkg).parent / 'config_defaults.py'
# site configuration
# TODO: !!!!!!!!!!!!!!!!!!!!!!!undo this... just for dev purposes
SITE_CONF_PATH: Path = Path('~/etc/cylc/hub/config.py').expanduser()
SITE_CONF_PATH: Path = Path('/etc/cylc/hub/config.py')
# user configuration
USER_CONF_PATH: Path = Path('~/.cylc/hub/config.py').expanduser()


def _load(path):
"""Load a configuration file."""
if path.exists():
Expand All @@ -45,14 +45,14 @@ def _load(path):

def load():
"""Load the relevant UIS/Hub configuration files."""
# if os.getenv('CYLC_SITE_CONF_PATH'):
# site_conf_path: Path = Path(
# os.environ['CYLC_SITE_CONF_PATH'],
# 'hub/config.py'
# )
# else:
# site_conf_path: Path = SITE_CONF_PATH
config_paths = [SITE_CONF_PATH, DEFAULT_CONF_PATH, USER_CONF_PATH]
if os.getenv('CYLC_SITE_CONF_PATH'):
site_conf_path: Path = Path(
os.environ['CYLC_SITE_CONF_PATH'],
'hub/config.py'
)
else:
site_conf_path: Path = SITE_CONF_PATH
config_paths = [DEFAULT_CONF_PATH, site_conf_path, USER_CONF_PATH]
for path in config_paths:
_load(path)

Expand Down
7 changes: 4 additions & 3 deletions cylc/uiserver/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
from jupyterhub.services.auth import HubOAuthenticated
from tornado import web, websocket
from tornado.ioloop import IOLoop
from functools import partial

from .websockets import authenticated as websockets_authenticated
from .authorise import AuthorizationMiddleware
Expand All @@ -35,6 +34,7 @@

ME = getpass.getuser()


class BaseHandler(HubOAuthenticated, web.RequestHandler):

def set_default_headers(self) -> None:
Expand Down Expand Up @@ -140,6 +140,7 @@ async def post(self) -> None:
except Exception as ex:
self.handle_error(ex)


class SubscriptionHandler(BaseHandler, websocket.WebSocketHandler):

def initialize(self, sub_server, resolvers, user_auth_config=None):
Expand All @@ -153,11 +154,11 @@ def select_subprotocol(self, subprotocols):
return GRAPHQL_WS

@websockets_authenticated
def get(self, *args, **kwargs):
def get(self, *args, **kwargs):
return websocket.WebSocketHandler.get(self, *args, **kwargs)

@websockets_authenticated
def open(self, *args, **kwargs):
def open(self, *args, **kwargs): # noqa: A003
IOLoop.current().spawn_callback(self.subscription_server.handle, self,
self.context)

Expand Down
47 changes: 18 additions & 29 deletions cylc/uiserver/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from logging.config import dictConfig
from pathlib import Path, PurePath
import sys
from typing import (Any, Dict, Tuple, Type, List, Union)
from typing import (Any, Dict, Tuple, Type, List)

from pkg_resources import parse_version
from tornado import web, ioloop
Expand Down Expand Up @@ -72,7 +72,6 @@
logger = logging.getLogger(__name__)



class MyApplication(web.Application):
is_closing = False

Expand Down Expand Up @@ -116,14 +115,15 @@ class CylcUIServer(Application):

site_authorization = Dict(
config=True,
help= '''
help='''
Dictionary containing site limits and defaults for authorisation.
''')

user_authorization = Dict(
config=True,
help='''
Dictionary containing authorised users and permission levels
Dictionary containing authorised users and permission levels for
authorisation.
'''
)

Expand Down Expand Up @@ -203,9 +203,9 @@ class CylcUIServer(Application):
'''
)

# @default('user_authorization')
# def _default_user_authorization(self):
# return {}
@default('site_authorization')
def _default_site_authorization(self):
return {}

@default('scan_interval')
def _default_scan_interval(self):
Expand All @@ -219,22 +219,22 @@ def _check_ui_build_dir_exists(self, proposed):

@validate('site_authorization')
def _check_site_auth_dict_correct_format(self, proposed):
# TODO: More advanced auth dict validating
if isinstance(proposed['value'], dict):
return proposed['value']
raise TraitError(f'Error in site authorization config: {proposed["value"]}')
raise TraitError(
f'Error in site authorization config: {proposed["value"]}')

@staticmethod
def _list_ui_versions(path: Path) -> List[str]:
"""Return a list of UI build versions detected in self.ui_path."""
return list(
sorted(
return sorted(
(
version.name
for version in path.glob('[0-9][0-9.]*')
if version
),
key=parse_version
)
)

@default('ui_path')
Expand Down Expand Up @@ -266,7 +266,6 @@ def _get_ui_path(self):

raise Exception(f'Could not find UI build in {ui_path}')


@default('logging_config')
def _default_logging_config(self):
return Path(Path(uis_pkg).parent / 'logging_config.json')
Expand All @@ -285,13 +284,8 @@ def __init__(self, port, jupyterhub_service_prefix, ui_build_dir=None):
self.data_store_mgr,
workflows_mgr=self.workflows_mgr)
self.authobj = Authorization(getpass.getuser(),
self.config.UIServer.user_authorization,
self.config.UIServer.site_authorization)



# more upfront processing of i.e. what user is permitted to permit
# into own function?
self.config.UIServer.user_authorization,
self.config.UIServer.site_authorization)

@staticmethod
@contextmanager
Expand All @@ -314,14 +308,13 @@ def _interim_log():
def _open_log(self):
"""Configure logging and open log handler(s)."""
if self.logging_config:
if self.logging_config.exists():
with open(self.logging_config, 'r') as logging_config_json:
config = json.load(logging_config_json)
dictConfig(config["logging"])
else:
if not self.logging_config.exists():
raise ValueError(
f'Logging config file not found: {self.logging_config}'
)
with open(self.logging_config, 'r') as logging_config_json:
config = json.load(logging_config_json)
dictConfig(config["logging"])

def _load_uis_config(self):
"""Load the UIS config file."""
Expand Down Expand Up @@ -440,7 +433,7 @@ def _make_app(self, debug: bool):
"graphql/batch",
UIServerGraphQLHandler,
batch=True,
auth = self.authobj
auth=self.authobj
),
# subscription/websockets handler
self._create_handler("subscriptions",
Expand Down Expand Up @@ -471,10 +464,6 @@ def start(self, debug: bool):
app = self._make_app(debug)
signal.signal(signal.SIGINT, app.signal_handler)
app.listen(self._port)

import mdb
mdb.debug()

# pass in server object for clean exit
ioloop.PeriodicCallback(
partial(app.try_exit, uis=self), 100).start()
Expand Down
Loading

0 comments on commit 3541eca

Please sign in to comment.