summaryrefslogtreecommitdiff
path: root/macaroonbakery/checkers/caveat.py
diff options
context:
space:
mode:
Diffstat (limited to 'macaroonbakery/checkers/caveat.py')
-rw-r--r--macaroonbakery/checkers/caveat.py125
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)