diff options
Diffstat (limited to 'macaroonbakery/checkers/caveat.py')
-rw-r--r-- | macaroonbakery/checkers/caveat.py | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/macaroonbakery/checkers/caveat.py b/macaroonbakery/checkers/caveat.py new file mode 100644 index 0000000..a1e564e --- /dev/null +++ b/macaroonbakery/checkers/caveat.py @@ -0,0 +1,125 @@ +# Copyright 2017 Canonical Ltd. +# Licensed under the LGPLv3, see LICENCE file for details. +import collections + +import pyrfc3339 + +from macaroonbakery.checkers.conditions import ( + STD_NAMESPACE, COND_TIME_BEFORE, COND_ERROR, COND_DENY, COND_ALLOW, + COND_DECLARED +) + + +class Caveat(collections.namedtuple('Caveat', 'condition location namespace')): + '''Represents a condition that must be true for a check to complete + successfully. + + If location is provided, the caveat must be discharged by + a third party at the given location (a URL string). + + The namespace parameter holds the namespace URI string of the + condition - if it is provided, it will be converted to a namespace prefix + before adding to the macaroon. + ''' + __slots__ = () + + def __new__(cls, condition, location=None, namespace=None): + return super(Caveat, cls).__new__(cls, condition, location, namespace) + + +def declared_caveat(key, value): + '''Returns a "declared" caveat asserting that the given key is + set to the given value. + + If a macaroon has exactly one first party caveat asserting the value of a + particular key, then infer_declared will be able to infer the value, and + then the check will allow the declared value if it has the value + specified here. + + If the key is empty or contains a space, it will return an error caveat. + ''' + if key.find(' ') >= 0 or key == '': + return error_caveat('invalid caveat \'declared\' key "{}"'.format(key)) + return _first_party(COND_DECLARED, key + ' ' + value) + + +def error_caveat(f): + '''Returns a caveat that will never be satisfied, holding f as the text of + the caveat. + + This should only be used for highly unusual conditions that are never + expected to happen in practice, such as a malformed key that is + conventionally passed as a constant. It's not a panic but you should + only use it in cases where a panic might possibly be appropriate. + + This mechanism means that caveats can be created without error + checking and a later systematic check at a higher level (in the + bakery package) can produce an error instead. + ''' + return _first_party(COND_ERROR, f) + + +def allow_caveat(ops): + ''' Returns a caveat that will deny attempts to use the macaroon to perform + any operation other than those listed. Operations must not contain a space. + ''' + if ops is None or len(ops) == 0: + return error_caveat('no operations allowed') + return _operation_caveat(COND_ALLOW, ops) + + +def deny_caveat(ops): + '''Returns a caveat that will deny attempts to use the macaroon to perform + any of the listed operations. Operations must not contain a space. + ''' + return _operation_caveat(COND_DENY, ops) + + +def _operation_caveat(cond, ops): + ''' Helper for allow_caveat and deny_caveat. + + It checks that all operation names are valid before creating the caveat. + ''' + for op in ops: + if op.find(' ') != -1: + return error_caveat('invalid operation name "{}"'.format(op)) + return _first_party(cond, ' '.join(ops)) + + +def time_before_caveat(t): + '''Return a caveat that specifies that the time that it is checked at + should be before t. + :param t is a a UTC date in - use datetime.utcnow, not datetime.now + ''' + + return _first_party(COND_TIME_BEFORE, + pyrfc3339.generate(t, accept_naive=True, + microseconds=True)) + + +def parse_caveat(cav): + ''' Parses a caveat into an identifier, identifying the checker that should + be used, and the argument to the checker (the rest of the string). + + The identifier is taken from all the characters before the first + space character. + :return two string, identifier and arg + ''' + if cav == '': + raise ValueError('empty caveat') + try: + i = cav.index(' ') + except ValueError: + return cav, '' + if i == 0: + raise ValueError('caveat starts with space character') + return cav[0:i], cav[i + 1:] + + +def _first_party(name, arg): + condition = name + if arg != '': + condition += ' ' + arg + + return Caveat(condition=condition, + namespace=STD_NAMESPACE) |