From 33f7883c235414056f56c1b7510a891d7e406079 Mon Sep 17 00:00:00 2001 From: Anderson Mesquita Date: Thu, 12 Mar 2015 13:04:09 +1000 Subject: [PATCH] Add Digest intrinsic function Digest intrinsic function takes an algorithm name (e.g. md5, sha512, etc) and a string value and will resolve to the corresponding hash of the value. Usage: pwd_hash: { digest: ['sha512', { get_param: raw_password }] } Implements: blueprint digest-intrinsic-function Change-Id: I7b5332462013cf8063fadec1654fa9f364da45de --- heat/engine/hot/functions.py | 42 ++++++++++++++++++++++++++++++++++++ heat/engine/hot/template.py | 1 + heat/tests/test_hot.py | 40 ++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/heat/engine/hot/functions.py b/heat/engine/hot/functions.py index 782b59c79..c50fa3fd7 100644 --- a/heat/engine/hot/functions.py +++ b/heat/engine/hot/functions.py @@ -12,6 +12,7 @@ # under the License. import collections +import hashlib import itertools import six @@ -334,3 +335,44 @@ class Repeat(function.Function): template = function.resolve(self._template) return [self._do_replacement(keys, items, template) for items in itertools.product(*lists)] + + +class Digest(function.Function): + ''' + A function for performing digest operations. + + Takes the form:: + + digest: + - + - + + Valid algorithms are the ones provided by natively by hashlib (md5, sha1, + sha224, sha256, sha384, and sha512) or any one provided by OpenSSL. + ''' + + def validate_usage(self, args): + if not (isinstance(args, list) and + all([isinstance(a, six.string_types) for a in args])): + msg = _('Argument to function "%s" must be a list of strings') + raise TypeError(msg % self.fn_name) + + if len(args) != 2: + msg = _('Function "%s" usage: ["", ""]') + raise ValueError(msg % self.fn_name) + + if args[0].lower() not in hashlib.algorithms: + msg = _('Algorithm must be one of %s') + raise ValueError(msg % six.text_type(hashlib.algorithms)) + + def digest(self, algorithm, value): + _hash = hashlib.new(algorithm) + _hash.update(value) + + return _hash.hexdigest() + + def result(self): + args = function.resolve(self.args) + self.validate_usage(args) + + return self.digest(*args) diff --git a/heat/engine/hot/template.py b/heat/engine/hot/template.py index 4f0067895..fdb558002 100644 --- a/heat/engine/hot/template.py +++ b/heat/engine/hot/template.py @@ -295,6 +295,7 @@ class HOTemplate20141016(HOTemplate20130523): class HOTemplate20150430(HOTemplate20141016): functions = { + 'digest': hot_funcs.Digest, 'get_attr': hot_funcs.GetAtt, 'get_file': hot_funcs.GetFile, 'get_param': hot_funcs.GetParam, diff --git a/heat/tests/test_hot.py b/heat/tests/test_hot.py index f97e04079..c820c00f1 100644 --- a/heat/tests/test_hot.py +++ b/heat/tests/test_hot.py @@ -671,6 +671,46 @@ class HOTemplateTest(common.HeatTestCase): 'for_each': {'%var%': ['a', 'b', 'c']}}} self.assertRaises(KeyError, self.resolve, snippet, tmpl) + def test_digest(self): + snippet = {'digest': ['md5', 'foobar']} + snippet_resolved = '3858f62230ac3c915f300c664312c63f' + + tmpl = parser.Template(hot_kilo_tpl_empty) + self.assertEqual(snippet_resolved, self.resolve(snippet, tmpl)) + + def test_digest_invalid_types(self): + tmpl = parser.Template(hot_kilo_tpl_empty) + + invalid_snippets = [ + {'digest': 'invalid'}, + {'digest': {'foo': 'invalid'}}, + {'digest': [123]}, + ] + for snippet in invalid_snippets: + exc = self.assertRaises(TypeError, self.resolve, snippet, tmpl) + self.assertIn('must be a list of strings', six.text_type(exc)) + + def test_digest_incorrect_number_arguments(self): + tmpl = parser.Template(hot_kilo_tpl_empty) + + invalid_snippets = [ + {'digest': []}, + {'digest': ['foo']}, + {'digest': ['md5']}, + {'digest': ['md5', 'foo', 'bar']}, + ] + for snippet in invalid_snippets: + exc = self.assertRaises(ValueError, self.resolve, snippet, tmpl) + self.assertIn('usage: ["", ""]', + six.text_type(exc)) + + def test_digest_invalid_algorithm(self): + tmpl = parser.Template(hot_kilo_tpl_empty) + + snippet = {'digest': ['invalid_algorithm', 'foobar']} + exc = self.assertRaises(ValueError, self.resolve, snippet, tmpl) + self.assertIn('Algorithm must be one of', six.text_type(exc)) + def test_prevent_parameters_access(self): """ Test that the parameters section can't be accessed using the template