Skip to content

Commit

Permalink
Merge pull request #24 from ewjoachim/document-restrictions
Browse files Browse the repository at this point in the history
  • Loading branch information
ewjoachim authored Mar 15, 2021
2 parents 1361a52 + e5f7ed9 commit 27b8428
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 46 deletions.
1 change: 1 addition & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ should not have to call the methods directly. Use `Token.restrict` and
`Token.restrictions` instead.

.. autoclass:: pypitoken.token.Restriction
:members: load_value, dump, dump_value, get_schema, extract_kwargs, check

.. autoclass:: pypitoken.NoopRestriction
:show-inheritance:
Expand Down
47 changes: 34 additions & 13 deletions pypitoken/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ class Restriction:
"""
Base Restriction class.
While Restriction intropection is part of the public API, methods on the
restriction class & subclasses are not meant to be used externally.
Expose lower-level methods for restriction/caveat introspection.
"""

def dump(self) -> str:
"""
Transform a restriction into a JSON-encoded string
"""
return json.dumps(self.dump_value())

@staticmethod
Expand All @@ -52,7 +54,19 @@ def get_schema() -> Dict:
raise NotImplementedError

@classmethod
def load_from_value(cls: Type[T], value: Any) -> T:
def load_value(cls: Type[T], value: Dict) -> T:
"""
Create a Restriction from the JSON value stored in the caveat
Raises
------
exceptions.LoaderError
Raise when the JSON format doesn't match this class' restriction format
Returns
-------
Restriction
"""
try:
jsonschema.validate(
instance=value,
Expand All @@ -67,7 +81,7 @@ def load_from_value(cls: Type[T], value: Any) -> T:
def extract_kwargs(cls, value: Dict) -> Dict:
"""
Receive the parsed JSON value of a caveat for which the schema has been
validated. Returns the instantiation kwargs (``__init__`` parameters).
validated. Return the instantiation kwargs (``__init__`` parameters).
"""
raise NotImplementedError

Expand All @@ -88,6 +102,9 @@ def check(self, context: Context) -> None:
raise NotImplementedError

def dump_value(self) -> Dict:
"""
Transform a restriction into a JSON object
"""
raise NotImplementedError


Expand Down Expand Up @@ -185,10 +202,10 @@ def json_load_caveat(caveat: str) -> Any:


def load_restriction(
caveat: str, classes: List[Type[Restriction]] = RESTRICTION_CLASSES
caveat: Dict, classes: List[Type[Restriction]] = RESTRICTION_CLASSES
) -> "Restriction":
"""
Create a Restriction from a caveat restriction string.
Create a Restriction from a raw caveat restriction JSON object.
Raises
------
Expand All @@ -197,17 +214,15 @@ def load_restriction(
Returns
-------
[type]
[description]
`Restriction`
"""
value = json_load_caveat(caveat=caveat)
for subclass in classes:
try:
return subclass.load_from_value(value=value)
return subclass.load_value(value=caveat)
except exceptions.LoaderError:
continue

raise exceptions.LoaderError(f"Could not find matching Restriction for {value}")
raise exceptions.LoaderError(f"Could not find matching Restriction for {caveat}")


def check_caveat(caveat: str, context: Context, errors: List[Exception]) -> bool:
Expand All @@ -233,7 +248,8 @@ def check_caveat(caveat: str, context: Context, errors: List[Exception]) -> bool
# To circumvent this, we store any exception in ``errors``

try:
restriction = load_restriction(caveat=caveat)
value = json_load_caveat(caveat=caveat)
restriction = load_restriction(caveat=value)
except exceptions.LoaderError as exc:
errors.append(exc)
return False
Expand Down Expand Up @@ -501,8 +517,13 @@ def restrictions(self) -> List[Restriction]:
Returns
-------
List[`Restriction`]
Raises
------
`pypitoken.LoaderError`
When the existing restrictions cannot be parsed
"""
return [
load_restriction(caveat=caveat.caveat_id)
load_restriction(caveat=json_load_caveat(caveat=caveat.caveat_id))
for caveat in self._macaroon.caveats
]
64 changes: 31 additions & 33 deletions tests/test_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def dump_value(self):
assert MyRestriction().dump() == '{"a": ["b"]}'


def test__Restriction__load_from_value__pass():
def test__Restriction__load_value__pass():
@dataclasses.dataclass
class MyRestriction(token.Restriction):
version: int
Expand All @@ -33,7 +33,7 @@ def get_schema():
def extract_kwargs(cls, value):
return {"version": value["version"]}

assert MyRestriction.load_from_value(value={"version": 42}).version == 42
assert MyRestriction.load_value(value={"version": 42}).version == 42


@pytest.mark.parametrize(
Expand All @@ -46,7 +46,7 @@ def extract_kwargs(cls, value):
{"version": 17},
],
)
def test__Restriction__load_from_value__fail(value):
def test__Restriction__load_value__fail(value):
class MyRestriction(token.Restriction):
@staticmethod
def get_schema():
Expand All @@ -59,13 +59,11 @@ def get_schema():
}

with pytest.raises(exceptions.LoaderError):
MyRestriction.load_from_value(value=value)
MyRestriction.load_value(value=value)


def test__NoopRestriction__load_from_value__pass():
tok = token.NoopRestriction.load_from_value(
value={"version": 1, "permissions": "user"}
)
def test__NoopRestriction__load_value__pass():
tok = token.NoopRestriction.load_value(value={"version": 1, "permissions": "user"})
assert tok == token.NoopRestriction()


Expand All @@ -80,9 +78,9 @@ def test__NoopRestriction__load_from_value__pass():
{"version": 1, "permissions": "user", "additional": "key"},
],
)
def test__NoopRestriction__load_from_value__fail(value):
def test__NoopRestriction__load_value__fail(value):
with pytest.raises(exceptions.LoaderError):
token.NoopRestriction.load_from_value(value=value)
token.NoopRestriction.load_value(value=value)


def test__NoopRestriction__extract_kwargs():
Expand Down Expand Up @@ -117,8 +115,8 @@ def test__NoopRestriction__dump_value():
),
],
)
def test__ProjectsRestriction__load_from_value__pass(value, restriction):
assert token.ProjectsRestriction.load_from_value(value=value) == restriction
def test__ProjectsRestriction__load_value__pass(value, restriction):
assert token.ProjectsRestriction.load_value(value=value) == restriction


@pytest.mark.parametrize(
Expand All @@ -136,9 +134,9 @@ def test__ProjectsRestriction__load_from_value__pass(value, restriction):
{"version": 1, "permissions": {"projects": ["a"], "additional": "key"}},
],
)
def test__ProjectsRestriction__load_from_value__fail(value):
def test__ProjectsRestriction__load_value__fail(value):
with pytest.raises(exceptions.LoaderError):
token.ProjectsRestriction.load_from_value(value=value)
token.ProjectsRestriction.load_value(value=value)


def test__ProjectsRestriction__extract_kwargs():
Expand Down Expand Up @@ -193,11 +191,11 @@ def test__json_load_caveat__fail():
"caveat, output",
[
(
'{"version": 1, "permissions": "user"}',
{"version": 1, "permissions": "user"},
token.NoopRestriction(),
),
(
'{"version": 1, "permissions": {"projects": ["a", "b"]}}',
{"version": 1, "permissions": {"projects": ["a", "b"]}},
token.ProjectsRestriction(projects=["a", "b"]),
),
],
Expand All @@ -206,23 +204,13 @@ def test__load_restriction__pass(caveat, output):
assert token.load_restriction(caveat=caveat) == output


@pytest.mark.parametrize(
"caveat, error",
[
(
"{",
"Error while loading caveat: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)",
),
(
'{"version": 1, "permissions": "something"}',
"Could not find matching Restriction for {'version': 1, 'permissions': 'something'}",
),
],
)
def test__load_restriction__fail(caveat, error):
def test__load_restriction__fail():
with pytest.raises(exceptions.LoaderError) as exc_info:
token.load_restriction(caveat=caveat)
assert str(exc_info.value) == error
token.load_restriction(caveat={"version": 1, "permissions": "something"})
assert (
str(exc_info.value)
== "Could not find matching Restriction for {'version': 1, 'permissions': 'something'}"
)


def test__check_caveat__pass():
Expand All @@ -236,7 +224,7 @@ def test__check_caveat__pass():
assert errors == []


def test__check_caveat__fail_load():
def test__check_caveat__fail_load_json():
errors = []
value = token.check_caveat("{", context=token.Context(project="a"), errors=errors)
assert value is False
Expand All @@ -248,6 +236,16 @@ def test__check_caveat__fail_load():
]


def test__check_caveat__fail_load():
errors = []
value = token.check_caveat(
'{"version": 13}', context=token.Context(project="a"), errors=errors
)
assert value is False
messages = [str(e) for e in errors]
assert messages == ["Could not find matching Restriction for {'version': 13}"]


def test__check_caveat__fail_check():
errors = []

Expand Down

0 comments on commit 27b8428

Please sign in to comment.