Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Migrate SECRET_KEY settings key to explicit state

From now on, a SECRET_KEY in settings should be used explicitly
in the configurations if a secret key is actually required.
  • Loading branch information...
commit 2fed8563151c0a57461e1007f2c98b7d538c5db9 1 parent c177444
@BasicWolf authored
View
3  kaylee/__init__.py
@@ -17,7 +17,7 @@
__version__ = '0.3'
from . import loader
-kl = loader.LazyKaylee()
+
from .core import Kaylee, Applications
from .node import Node, NodeID, NodesRegistry
@@ -33,6 +33,7 @@
NodeRequestRejectedError,
ApplicationCompletedError,)
+kl = loader.LazyKaylee()
def setup(settings):
#pylint: disable-msg=W0212
View
4 kaylee/client/kaylee.coffee
@@ -20,7 +20,7 @@
# CONSTANTS #
#-----------#
-SESSION_DATA_ATTRIBUTE = '__kl_sd__'
+SESSION_DATA_ATTRIBUTE = '__kl_session_data__'
WORKER_SCRIPT_URL = ((scripts) ->
scripts = document.getElementsByTagName('script')
@@ -29,7 +29,7 @@ WORKER_SCRIPT_URL = ((scripts) ->
if not script.getAttribute.length?
path = script.src
- # replace 'http://address/kaylee.js' with 'http://address/klworker.js'
+ # replace 'http://path/to/kaylee.js' with 'http://path/to/klworker.js'
path = script.getAttribute('src', -1)
return path[..path.lastIndexOf('/')] + 'klworker.js'
)()
View
4 kaylee/controller.py
@@ -8,10 +8,10 @@
:copyright: (c) 2012 by Zaur Nasibov.
:license: MIT, see LICENSE for more details.
"""
-
import re
from abc import ABCMeta, abstractmethod
+
#: The Application name regular expression pattern which can be used in
#: e.g. web frameworks' URL dispatchers.
app_name_pattern = r'[a-zA-Z\.\d_-]+'
@@ -55,7 +55,7 @@ class Controller(object, metaclass=ABCMeta):
_app_name_re = re.compile('^{}$'.format(app_name_pattern))
def __init__(self, name, project, permanent_storage,
- temporal_storage=None):
+ temporal_storage=None, **kwargs):
if Controller._app_name_re.match(name) is None:
raise ValueError('Invalid application name: {}'
.format(name))
View
29 kaylee/core.py
@@ -20,8 +20,8 @@
from functools import wraps
from .node import Node, NodeID
-from .errors import (KayleeError, InvalidResultError, NodeRequestRejectedError,
- InvalidConfigurationError)
+from .errors import (KayleeError, InvalidResultError, NodeRequestRejectedError)
+
from .controller import KL_RESULT
from .util import DictAsObjectWrapper
@@ -114,7 +114,7 @@ def register(self, remote_host):
node = Node(NodeID.for_host(remote_host))
self.registry.add(node)
return json.dumps ({ 'node_id' : str(node.id),
- 'config' : self.config.to_client_dict(),
+ 'config' : self.config.client_config(),
'applications' : self._applications.names } )
@json_error_handler
@@ -262,23 +262,10 @@ class Config(DictAsObjectWrapper):
"""The ``Config`` object maintains the run-time Kaylee
configuration options (see :ref:`configuration` for full description).
"""
- client_config_fields = [
- 'AUTO_GET_ACTION',
- ]
-
def __init__(self, **kwargs):
super(Config, self).__init__(**kwargs)
self._dirty = True
self._cached_dict = {}
- self._validate()
-
- def _validate(self):
- #pylint: disable-msg=E1101
- if not isinstance(self.AUTO_GET_ACTION, bool):
- raise InvalidConfigurationError('AUTO_GET_ACTION is not a boolean')
- #pylint: disable-msg=E1101
- if not isinstance(self.SECRET_KEY, str):
- raise InvalidConfigurationError('SECRET_KEY is not a string object')
def __setattr__(self, name, value):
if name != '_dirty':
@@ -287,10 +274,14 @@ def __setattr__(self, name, value):
else:
self.__dict__[name] = value
- def to_client_dict(self):
+ def client_config(self):
+ client_config_fields = [
+ 'AUTO_GET_ACTION',
+ ]
+
if self._dirty:
- self._cached_dict = { k : getattr(self, k)
- for k in self.client_config_fields }
+ self._cached_dict = {k : getattr(self, k)
+ for k in client_config_fields}
self._dirty = False
return self._cached_dict
View
9 kaylee/errors.py
@@ -72,12 +72,11 @@ def __init__(self, application):
.format(application.name) )
-class InvalidConfigurationError(KayleeError):
- """Raised when :class:`Kaylee object <Kaylee>` configuration is
- configured improperly."""
+class SettingsError(KayleeError):
+ """Raised when Kaylee settings are invalid"""
def __init__(self, message):
- super(InvalidConfigurationError, self).__init__(
- 'Invalid configuration: ' + message)
+ super(SettingsError, self).__init__(
+ 'Invalid settings: ' + message)
class SessionKeyNameError(KayleeError):
View
43 kaylee/loader.py
@@ -19,16 +19,14 @@
import kaylee.contrib
from .core import Kaylee
-from .errors import KayleeError
-from .util import LazyObject, is_strong_subclass
+from .errors import KayleeError, SettingsError
+from .util import (LazyObject, is_strong_subclass, MIN_SECRET_KEY_LENGTH,)
from . import storage, controller, project, node, session
import logging
log = logging.getLogger(__name__)
-_default_settings = {
- 'AUTO_GET_ACTION' : True,
-}
+
class LazyKaylee(LazyObject):
@@ -75,10 +73,9 @@ def load(settings):
str.__name__,
type(settings).__name__))
- new_settings = deepcopy(_default_settings)
- new_settings.update(settings)
try:
- loader = Loader(new_settings)
+ SettingsValidator.validate(settings)
+ loader = Loader(settings)
registry = loader.registry
sdm = loader.session_data_manager
apps = loader.applications
@@ -91,8 +88,32 @@ def load(settings):
**settings)
-
-class Loader(object):
+class SettingsValidator:
+ @staticmethod
+ def validate(settings):
+ SettingsValidator.validate_AUTO_GET_ACTION(settings)
+ SettingsValidator.validate_SECRET_KEY(settings)
+
+ @staticmethod
+ def validate_AUTO_GET_ACTION(settings):
+ val = settings['AUTO_GET_ACTION']
+ if not isinstance(val, bool):
+ raise SettingsError('AUTO_GET_ACTION is not a boolean')
+
+ @staticmethod
+ def validate_SECRET_KEY(settings):
+ if 'SECRET_KEY' not in settings:
+ return
+ val = settings['SECRET_KEY']
+ if not isinstance(val, str):
+ raise SettingsError('SECRET_KEY is not a string object')
+ if len(val) < MIN_SECRET_KEY_LENGTH:
+ raise SettingsError('SECRET_KEY is too short (at least {} '
+ 'characters are required)'
+ .format(MIN_SECRET_KEY_LENGTH))
+
+
+class Loader:
_loadable_base_classes = [
project.Project,
controller.Controller,
@@ -105,7 +126,6 @@ class Loader(object):
def __init__(self, settings):
self._classes = defaultdict(dict)
self._settings = settings
-
# load classes from contrib (non-refreshable)
self._update_classes(kaylee.contrib)
# load session data managers
@@ -116,6 +136,7 @@ def __init__(self, settings):
for mod in find_modules(projects_dir):
self._update_classes(mod)
+
@property
def registry(self):
settings = self._settings
View
5 kaylee/manager/commands/start_env.py
@@ -2,8 +2,7 @@
import stat
from jinja2 import Template
from kaylee.manager import AdminCommand
-from kaylee.util import random_string
-
+from kaylee.util import generate_sercret_key
class StartEnvCommand(AdminCommand):
name = 'startenv'
@@ -36,7 +35,7 @@ def start_env(opts):
os.mkdir(dest_path)
render_args = {
- 'SECRET_KEY' : random_string(32),
+ 'SECRET_KEY' : generate_sercret_key(),
'PROJECTS_DIR' : dest_path,
}
View
3  kaylee/manager/commands/templates/env_template/settings.py
@@ -2,7 +2,8 @@
# when a result is accepted from a node.
AUTO_GET_ACTION = True
-# The key used for session encryption etc.
+# A string that can be explicitly used in all the configurations
+# which require a secret key (for encryption, signing etc).
SECRET_KEY = '{{ SECRET_KEY }}'
# A directory in which Kaylee searches for user projects
View
41 kaylee/session.py
@@ -22,11 +22,11 @@
from Crypto.Cipher import AES
from abc import ABCMeta, abstractmethod
-from .util import get_secret_key
+from .util import random_string
from .errors import KayleeError, SessionKeyNameError
-SESSION_DATA_ATTRIBUTE = '__kl_sd__'
+SESSION_DATA_ATTRIBUTE = '__kl_session_data__'
class SessionDataManager(object, metaclass=ABCMeta):
@@ -94,6 +94,22 @@ def remove_session_data_from_task(session_data_keys, task):
del task[key]
+
+class EncryptedSessionDataManager(SessionDataManager):
+ """The implementation of this abstract class is a
+ session data manager which encrypts all stored session data.
+
+ :param secret_key: A key used to encrypt the data. Generated automatically
+ if not supplied
+ (see :attr:`EncryptedSessionDataManager.SECRET_KEY_LENGTH`).
+ :type secret_key: str
+ """
+
+ def __init__(self, secret_key):
+ self.secret_key = secret_key
+ super(EncryptedSessionDataManager, self).__init__()
+
+
class PhonySessionDataManager(SessionDataManager):
"""The default session data manager which throws :class:`KayleeError`
if any session variables are encountered in an outgoing task."""
@@ -130,7 +146,7 @@ def restore(self, node, result):
result.update(session_data)
-class ClientSessionDataManager(SessionDataManager):
+class ClientSessionDataManager(EncryptedSessionDataManager):
"""Stores encrypted session variables in task and restores them
from the results. For example, the following task data::
@@ -144,10 +160,10 @@ class ClientSessionDataManager(SessionDataManager):
task = {
id: 'i1',
- '__kl_sd__': 'yn/fCyEcW8AFrPps7XoxunC...' # 143 chars in total
+ '#__kl_sd__': 'yn/fCyEcW8AFrPps7XoxunC...' # 143 chars in total
}
- The Kaylee client-side engine automatically attaches the ``'__kl_sd__``
+ The Kaylee client-side engine automatically attaches the ``'#__kl_sd__``
data to the JSON result sent to the server, so that the session data
could be decrypted and restored, e.g.::
@@ -156,16 +172,13 @@ class ClientSessionDataManager(SessionDataManager):
'#s1': 10,
'#s2': [1, 2, 3]
}
-
- :param secret_key: An override of the global :config:`SECRET_KEY`
- parameter.
"""
- def __init__(self, secret_key=None):
+ def __init__(self, secret_key):
#pylint: disable-msg=W0231
#W0231: __init__ method from base class 'SessionDataManager'
# is not called.
- self._secret_key = secret_key
self.SESSION_DATA_ATTRIBUTE = SESSION_DATA_ATTRIBUTE
+ super(ClientSessionDataManager, self).__init__(secret_key)
def store(self, node, task):
session_data = self.get_session_data(task)
@@ -183,14 +196,6 @@ def restore(self, node, result):
del result[self.SESSION_DATA_ATTRIBUTE]
result.update(sd)
- @property
- def secret_key(self):
- if self._secret_key is None:
- self._secret_key = get_secret_key()
- return self._secret_key
-
-
-
def _encrypt(data, secret_key):
"""Encrypt the data and return its base64 representation.
View
26 kaylee/testsuite/loader_tests.py
@@ -6,12 +6,14 @@
from kaylee.testsuite import KayleeTest, load_tests, PROJECTS_DIR
import os
-from kaylee import loader, Kaylee
+from kaylee import loader, Kaylee, KayleeError
+from kaylee.errors import SettingsError
from kaylee.contrib import (MemoryTemporalStorage,
MemoryPermanentStorage,
MemoryNodesRegistry)
from kaylee.session import ClientSessionDataManager
-from kaylee.loader import Loader
+from kaylee.loader import Loader, SettingsValidator
+from kaylee.util import generate_sercret_key
_test_REGISTRY = {
'name' : 'MemoryNodesRegistry',
@@ -26,11 +28,13 @@ class TestSettings(object):
AUTO_GET_ACTION = True
- SECRET_KEY = '1234'
+ SECRET_KEY = generate_sercret_key()
SESSION_DATA_MANAGER = {
'name' : 'ClientSessionDataManager',
- 'config' : {}
+ 'config' : {
+ 'secret_key' : SECRET_KEY
+ }
}
@@ -39,7 +43,7 @@ class TestSettingsWithApps(object):
AUTO_GET_ACTION = True
- SECRET_KEY = '1234'
+ SECRET_KEY = generate_sercret_key()
APPLICATIONS = [
{
@@ -132,4 +136,16 @@ def test_kaylee_setup(self):
self.assertEqual(app.project.__class__.__name__, 'AutoTestProject')
+ def test_settings_validator(self):
+ sv = SettingsValidator
+ self.assertRaises(SettingsError, sv.validate_AUTO_GET_ACTION, {'AUTO_GET_ACTION': 10})
+ # self.assertRaises(KayleeError, Settings, SECRET_KEY=123)
+ # self.assertRaises(KayleeError, Settings, SECRET_KEY='abc')
+
+ # self.assertIsNone(_validate({
+ # 'SECRET_KEY': ' ',
+ # 'AUTO_GET_ACTION' : True
+ # }))
+
+
kaylee_suite = load_tests([KayleeLoaderTests])
View
6 kaylee/testsuite/session_tests.py
@@ -4,7 +4,7 @@
from kaylee import KayleeError
from kaylee.session import (_encrypt, _decrypt, ClientSessionDataManager,
ServerSessionDataManager, PhonySessionDataManager,
- SESSION_DATA_ATTRIBUTE,
+ SESSION_DATA_ATTRIBUTE, EncryptedSessionDataManager,
SessionDataManager,)
from kaylee.errors import SessionKeyNameError
@@ -121,6 +121,10 @@ def test_json_session_data_manager(self):
jsdm.restore(node, task)
self.assertEqual(task, orig_task)
+ jsdm2 = ClientSessionDataManager(secret_key='abc')
+ self.assertIsInstance(jsdm2, EncryptedSessionDataManager)
+ self.assertEqual(len(jsdm2.secret_key), (len('abc')))
+
def test_phony_session_data_manager(self):
node = Node(NodeID.for_host('127.0.0.1'))
task1 = {
View
28 kaylee/testsuite/util_tests.py
@@ -10,7 +10,7 @@
import kaylee
from kaylee.testsuite import KayleeTest, load_tests
from kaylee.util import (parse_timedelta, LazyObject, random_string,
- get_secret_key, DictAsObjectWrapper,
+ DictAsObjectWrapper,
RecursiveDictAsObjectWrapper)
from kaylee import KayleeError
@@ -125,32 +125,6 @@ def test_random_string(self):
for c in s:
self.assertIn(c, special)
- def test_get_secret_key(self):
- sk = get_secret_key('abc')
- self.assertEqual(sk, 'abc')
-
- # test when config is not loaded
- self.assertRaises(KayleeError, get_secret_key)
-
- # test loading from config
- from kaylee.testsuite import test_settings
- from kaylee import setup
- setup(test_settings)
- sk = get_secret_key()
- self.assertEqual(sk, test_settings.SECRET_KEY)
-
- # test if default parameter works after previous call
- sk = get_secret_key('abc')
- self.assertEqual(sk, 'abc')
-
- # test for proper behaviour after releasing the object from proxy
- setup(None)
- self.assertRaises(KayleeError, get_secret_key)
-
- # and the final test :)
- sk = get_secret_key('abc')
- self.assertEqual(sk, 'abc')
-
def test_dict_as_object_wrapper(self):
#pylint: disable-msg=E1101
#E1101 Instance of 'DictAsObjectWrapper' has no 'A' member
View
20 kaylee/util.py
@@ -26,6 +26,9 @@
from datetime import timedelta
from .errors import KayleeError
+
+MIN_SECRET_KEY_LENGTH = 32
+
def parse_timedelta(s):
try:
match = parse_timedelta.timeout_regex.match(s)
@@ -150,21 +153,8 @@ def random_string(length, alphabet=None, lowercase=True, uppercase=True,
return ''.join(random.choice(src) for x in range(length))
-def get_secret_key(key=None):
- if key is not None:
- return key
- else:
- from kaylee import kl
- if kl._wrapped is None:
- raise KayleeError('Cannot locate a valid secret key because '
- 'Kaylee proxy object has not beet set up.')
- key = kl.config.SECRET_KEY
- if key is None:
- raise KayleeError('SECRET_KEY configuration option is not '
- 'defined.')
- return key
-
-
+def generate_sercret_key():
+ return random_string(MIN_SECRET_KEY_LENGTH)
def is_strong_subclass(C, B):
"""Return whether class C is a subclass (i.e., a derived class) of class B
Please sign in to comment.
Something went wrong with that request. Please try again.