Skip to content

Commit

Permalink
Merge pull request #1099 from girder/better-yaml-config-access
Browse files Browse the repository at this point in the history
Better expose the yaml config files from python
  • Loading branch information
manthey authored Apr 7, 2023
2 parents ca5a4aa + 3478be0 commit 0be1c49
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 103 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
### Changes
- On the large image item page, guard against overflow ([#1096](../../pull/1096))
- Specify version of simplejpeg for older python ([#1098](../../pull/1098))
- Better expose the Girder yaml config files from python ([#1099](../../pull/1099))

## 1.20.3

Expand Down
109 changes: 108 additions & 1 deletion girder/girder_large_image/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@
import re
import warnings

import yaml
from girder_jobs.constants import JobStatus
from girder_jobs.models.job import Job

import girder
import large_image
from girder import events, logger
from girder.constants import AccessType
from girder.constants import AccessType, SortDir
from girder.exceptions import RestException, ValidationException
from girder.models.file import File
from girder.models.folder import Folder
from girder.models.group import Group
from girder.models.item import Item
from girder.models.notification import Notification
from girder.models.setting import Setting
Expand Down Expand Up @@ -367,6 +369,111 @@ def metadataSearchHandler( # noqa
return result


def _mergeDictionaries(a, b):
"""
Merge two dictionaries recursively. If the second dictionary (or any
sub-dictionary) has a special key, value of '__all__': True, the updated
dictionary only contains values from the second dictionary and excludes
the __all__ key.
:param a: the first dictionary. Modified.
:param b: the second dictionary that gets added to the first.
:returns: the modified first dictionary.
"""
if b.get('__all__') is True:
a.clear()
for key in b:
if isinstance(a.get(key), dict) and isinstance(b[key], dict):
_mergeDictionaries(a[key], b[key])
elif key != '__all__' or b[key] is not True:
a[key] = b[key]
return a


def adjustConfigForUser(config, user):
"""
Given the current user, adjust the config so that only relevant and
combined values are used. If the root of the config dictionary contains
"access": {"user": <dict>, "admin": <dict>}, the base values are updated
based on the user's access level. If the root of the config contains
"group": {<group-name>: <dict>, ...}, the base values are updated for
every group the user is a part of.
The order of update is groups in C-sort alphabetical order followed by
access/user and then access/admin as they apply.
:param config: a config dictionary.
"""
if not isinstance(config, dict):
return config
if isinstance(config.get('groups'), dict):
groups = config.pop('groups')
if user:
for group in Group().find(
{'_id': {'$in': user['groups']}}, sort=[('name', SortDir.ASCENDING)]):
if isinstance(groups.get(group['name']), dict):
config = _mergeDictionaries(config, groups[group['name']])
if isinstance(config.get('access'), dict):
accessList = config.pop('access')
if user and isinstance(accessList.get('user'), dict):
config = _mergeDictionaries(config, accessList['user'])
if user and user.get('admin') and isinstance(accessList.get('admin'), dict):
config = _mergeDictionaries(config, accessList['admin'])
return config


def yamlConfigFile(folder, name, user):
"""
Get a resolved named config file based on a folder and user.
:param folder: a Girder folder model.
:param name: the name of the config file.
:param user: the user that the response if adjusted for.
:returns: either None if no config file, or a yaml record.
"""
addConfig = None
last = False
while folder:
item = Item().findOne({'folderId': folder['_id'], 'name': name})
if item:
for file in Item().childFiles(item):
if file['size'] > 10 * 1024 ** 2:
logger.info('Not loading %s -- too large' % file['name'])
continue
with File().open(file) as fptr:
config = yaml.safe_load(fptr)
if isinstance(config, list) and len(config) == 1:
config = config[0]
# combine and adjust config values based on current user
if isinstance(config, dict) and 'access' in config or 'group' in config:
config = adjustConfigForUser(config, user)
if addConfig and isinstance(config, dict):
config = _mergeDictionaries(config, addConfig)
if not isinstance(config, dict) or config.get('__inherit__') is not True:
return config
config.pop('__inherit__')
addConfig = config
if last:
break
if folder['parentCollection'] != 'folder':
if folder['name'] != '.config':
folder = Folder().findOne({
'parentId': folder['parentId'],
'parentCollection': folder['parentCollection'],
'name': '.config'})
else:
last = 'setting'
if not folder or last == 'setting':
folderId = Setting().get(constants.PluginSettings.LARGE_IMAGE_CONFIG_FOLDER)
if not folderId:
break
folder = Folder().load(folderId, force=True)
last = True
else:
folder = Folder().load(folder['parentId'], user=user, level=AccessType.READ)
return addConfig


# Validators

@setting_utilities.validator({
Expand Down
106 changes: 4 additions & 102 deletions girder/girder_large_image/rest/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import json

import yaml

from girder import logger
from girder.api import access
from girder.api.describe import Description, autoDescribeRoute
from girder.api.rest import boundHandler
from girder.constants import AccessType, SortDir, TokenScope
from girder.models.file import File
from girder.constants import AccessType, TokenScope
from girder.models.folder import Folder
from girder.models.group import Group
from girder.models.item import Item
from girder.models.setting import Setting

from .. import constants


def addSystemEndpoints(apiRoot):
Expand Down Expand Up @@ -103,59 +96,6 @@ def _itemFindRecursive(self, origItemFind, folderId, text, name, limit, offset,
return origItemFind(folderId, text, name, limit, offset, sort, filters)


def _mergeDictionaries(a, b):
"""
Merge two dictionaries recursively. If the second dictionary (or any
sub-dictionary) has a special key, value of '__all__': True, the updated
dictionary only contains values from the second dictionary and excludes
the __all__ key.
:param a: the first dictionary. Modified.
:param b: the second dictionary that gets added to the first.
:returns: the modified first dictionary.
"""
if b.get('__all__') is True:
a.clear()
for key in b:
if isinstance(a.get(key), dict) and isinstance(b[key], dict):
_mergeDictionaries(a[key], b[key])
elif key != '__all__' or b[key] is not True:
a[key] = b[key]
return a


def adjustConfigForUser(config, user):
"""
Given the current user, adjust the config so that only relevant and
combined values are used. If the root of the config dictionary contains
"access": {"user": <dict>, "admin": <dict>}, the base values are updated
based on the user's access level. If the root of the config contains
"group": {<group-name>: <dict>, ...}, the base values are updated for
every group the user is a part of.
The order of update is groups in C-sort alphabetical order followed by
access/user and then access/admin as they apply.
:param config: a config dictionary.
"""
if not isinstance(config, dict):
return config
if isinstance(config.get('groups'), dict):
groups = config.pop('groups')
if user:
for group in Group().find(
{'_id': {'$in': user['groups']}}, sort=[('name', SortDir.ASCENDING)]):
if isinstance(groups.get(group['name']), dict):
config = _mergeDictionaries(config, groups[group['name']])
if isinstance(config.get('access'), dict):
accessList = config.pop('access')
if user and isinstance(accessList.get('user'), dict):
config = _mergeDictionaries(config, accessList['user'])
if user and user.get('admin') and isinstance(accessList.get('admin'), dict):
config = _mergeDictionaries(config, accessList['admin'])
return config


@access.public(scope=TokenScope.DATA_READ)
@autoDescribeRoute(
Description('Get a config file.')
Expand All @@ -179,45 +119,7 @@ def adjustConfigForUser(config, user):
)
@boundHandler()
def getYAMLConfigFile(self, folder, name):
addConfig = None
from .. import yamlConfigFile

user = self.getCurrentUser()
last = False
while folder:
item = Item().findOne({'folderId': folder['_id'], 'name': name})
if item:
for file in Item().childFiles(item):
if file['size'] > 10 * 1024 ** 2:
logger.info('Not loading %s -- too large' % file['name'])
continue
with File().open(file) as fptr:
config = yaml.safe_load(fptr)
if isinstance(config, list) and len(config) == 1:
config = config[0]
# combine and adjust config values based on current user
if isinstance(config, dict) and 'access' in config or 'group' in config:
config = adjustConfigForUser(config, user)
if addConfig and isinstance(config, dict):
config = _mergeDictionaries(config, addConfig)
if not isinstance(config, dict) or config.get('__inherit__') is not True:
return config
config.pop('__inherit__')
addConfig = config
if last:
break
if folder['parentCollection'] != 'folder':
if folder['name'] != '.config':
folder = Folder().findOne({
'parentId': folder['parentId'],
'parentCollection': folder['parentCollection'],
'name': '.config'})
else:
last = 'setting'
if not folder or last == 'setting':
folderId = Setting().get(constants.PluginSettings.LARGE_IMAGE_CONFIG_FOLDER)
if not folderId:
break
folder = Folder().load(folderId, force=True)
last = True
else:
folder = Folder().load(folder['parentId'], user=user, level=AccessType.READ)
return addConfig
return yamlConfigFile(folder, name, user)

0 comments on commit 0be1c49

Please sign in to comment.