#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
# Copyright Kitware Inc.
#
# Licensed under the Apache License, Version 2.0 ( the "License" );
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###############################################################################
from collections import OrderedDict
import cherrypy
from ..constants import SettingDefault, SettingKey
from .model_base import Model, ValidationException
from girder.utility import camelcase, plugin_utilities
[docs]class Setting(Model):
"""
This model represents server-wide configuration settings as key/value pairs.
"""
def initialize(self):
self.name = 'setting'
self.ensureIndices(['key'])
self._corsSettingsCache = None
[docs] def validate(self, doc):
"""
This method is in charge of validating that the setting key is a valid
key, and that for that key, the provided value is valid. It first
allows plugins to validate the setting, but if none of them can, it
assumes it is a core setting and does the validation here.
"""
key = doc['key']
funcName = 'validate'+camelcase(key)
if callable(getattr(self, funcName, None)):
getattr(self, funcName)(doc)
else:
raise ValidationException(
'Invalid setting key "{}".'.format(key), 'key')
return doc
[docs] def validateCorePluginsEnabled(self, doc):
"""
Ensures that the set of plugins passed in is a list of valid plugin
names. Removes any invalid plugin names, removes duplicates, and adds
all transitive dependencies to the enabled list.
"""
if not type(doc['value']) is list:
raise ValidationException(
'Plugins enabled setting must be a list.', 'value')
allPlugins = plugin_utilities.findAllPlugins()
doc['value'] = set(doc['value'])
def addDeps(plugin):
for dep in allPlugins[plugin]['dependencies']:
if dep not in doc['value']:
doc['value'].add(dep)
addDeps(dep)
for enabled in list(doc['value']):
if enabled in allPlugins:
addDeps(enabled)
else:
doc['value'].remove(enabled)
doc['value'] = list(doc['value'])
def validateCoreCookieLifetime(self, doc):
try:
doc['value'] = int(doc['value'])
if doc['value'] > 0:
return
except ValueError:
pass # We want to raise the ValidationException
raise ValidationException(
'Cookie lifetime must be an integer > 0.', 'value')
def validateCoreCorsAllowMethods(self, doc):
if isinstance(doc['value'], basestring):
methods = doc['value'].replace(",", " ").strip().upper().split()
# remove duplicates
methods = list(OrderedDict.fromkeys(methods))
doc['value'] = ", ".join(methods)
self.corsSettingsCacheClear()
return
raise ValidationException(
'Allowed methods must be a comma-separated list or an empty '
'string.', 'value')
def validateCoreCorsAllowHeaders(self, doc):
if isinstance(doc['value'], basestring):
headers = doc['value'].replace(",", " ").strip().split()
# remove duplicates
headers = list(OrderedDict.fromkeys(headers))
doc['value'] = ", ".join(headers)
self.corsSettingsCacheClear()
return
raise ValidationException(
'Allowed headers must be a comma-separated list or an empty '
'string.', 'value')
def validateCoreCorsAllowOrigin(self, doc):
if isinstance(doc['value'], basestring):
origins = doc['value'].replace(",", " ").strip().split()
origins = [origin.rstrip('/') for origin in origins]
# remove duplicates
origins = list(OrderedDict.fromkeys(origins))
doc['value'] = ", ".join(origins)
self.corsSettingsCacheClear()
return
raise ValidationException(
'Allowed origin must be a comma-separated list of base urls or * '
'or an empty string.', 'value')
def validateCoreEmailFromAddress(self, doc):
if not doc['value']:
raise ValidationException(
'Email from address must not be blank.', 'value')
def validateCoreEmailHost(self, doc):
if isinstance(doc['value'], basestring):
doc['value'] = doc['value'].strip()
return
raise ValidationException(
'Email host must be a string.', 'value')
def defaultCoreEmailHost(self):
if (cherrypy.request and cherrypy.request.local and
cherrypy.request.local.name):
host = '://'.join((cherrypy.request.scheme,
cherrypy.request.local.name))
if cherrypy.request.local.port != 80:
host += ':{}'.format(cherrypy.request.local.port)
return host
def validateCoreRegistrationPolicy(self, doc):
doc['value'] = doc['value'].lower()
if doc['value'] not in ('open', 'closed'):
raise ValidationException(
'Registration policy must be either "open" or "closed".',
'value')
def validateCoreSmtpHost(self, doc):
if not doc['value']:
raise ValidationException(
'SMTP host must not be blank.', 'value')
def validateCoreUploadMinimumChunkSize(self, doc):
try:
doc['value'] = int(doc['value'])
if doc['value'] >= 0:
return
except ValueError:
pass # We want to raise the ValidationException
raise ValidationException(
'Upload minimum chunk size must be an integer >= 0.',
'value')
[docs] def get(self, key, default='__default__'):
"""
Retrieve a setting by its key.
:param key: The key identifying the setting.
:type key: str
:param default: If no such setting exists, returns this value instead.
:returns: The value, or the default value if the key is not found.
"""
setting = self.findOne({'key': key})
if setting is None:
if default is '__default__':
default = self.getDefault(key)
return default
else:
return setting['value']
[docs] def set(self, key, value):
"""
Save a setting. If a setting for this key already exists, this will
replace the existing value.
:param key: The key identifying the setting.
:type key: str
:param value: The object to store for this setting.
:returns: The document representing the saved Setting.
"""
setting = self.findOne({'key': key})
if setting is None:
setting = {
'key': key,
'value': value
}
else:
setting['value'] = value
return self.save(setting)
[docs] def unset(self, key):
"""
Remove the setting for this key. If no such setting exists, this is
a no-op.
:param key: The key identifying the setting to be removed.
:type key: str
"""
for setting in self.find({'key': key}):
self.remove(setting)
[docs] def getDefault(self, key):
"""
Retreive the system default for a value.
:param key: The key identifying the setting.
:type key: str
:returns: The default value if the key is present in both SettingKey
and referenced in SettingDefault; otherwise None.
"""
default = None
if key in SettingDefault.defaults:
default = SettingDefault.defaults[key]
else:
funcName = 'default'+camelcase(key)
if callable(getattr(self, funcName, None)):
default = getattr(self, funcName)()
return default