Skip to content

Changes as an effect of changing persistent storage model. #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Mar 21, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ exclude_lines = [

[tool.poetry]
name = "cryptojwt"
version = "1.4.1"
version = "1.4.2"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a minor version bump, 1.5.0 perhaps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

description = "Python implementation of JWT, JWE, JWS and JWK"
authors = ["Roland Hedberg <roland@catalogix.se>"]
license = "Apache-2.0"
Expand Down
100 changes: 75 additions & 25 deletions src/cryptojwt/key_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import time
from datetime import datetime
from functools import cmp_to_key
from typing import Optional

import requests

Expand All @@ -24,7 +25,6 @@
from .jwk.jwk import dump_jwk
from .jwk.jwk import import_jwk
from .jwk.rsa import RSAKey
from .jwk.rsa import import_private_rsa_key_from_file
from .jwk.rsa import new_rsa_key
from .utils import as_unicode

Expand Down Expand Up @@ -189,22 +189,22 @@ def __init__(
"""

self._keys = []
self.remote = False
self.local = False
self.cache_time = cache_time
self.ignore_errors_period = ignore_errors_period
self.ignore_errors_until = None # UNIX timestamp of last error
self.time_out = 0
self.etag = ""
self.source = None
self.fileformat = fileformat.lower()
self.ignore_errors_period = ignore_errors_period
self.ignore_errors_until = None # UNIX timestamp of last error
self.ignore_invalid_keys = ignore_invalid_keys
self.imp_jwks = None
self.keytype = keytype
self.keyusage = keyusage
self.imp_jwks = None
self.last_updated = 0
self.last_remote = None # HTTP Date of last remote update
self.last_local = None # UNIX timestamp of last local update
self.ignore_invalid_keys = ignore_invalid_keys
self.last_remote = None # HTTP Date of last remote update
self.last_updated = 0
self.local = False
self.remote = False
self.source = None
self.time_out = 0

if httpc:
self.httpc = httpc
Expand Down Expand Up @@ -751,7 +751,7 @@ def difference(self, bundle):

return [k for k in self._keys if k not in bundle]

def dump(self):
def dump(self, cutoff: Optional[list] = None):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat confusing attribute name (at least for me). Perhaps excluded_attrs is better?

The type should be Optional[List[str]] I think (I assume attribute names are strings)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Horticultural term :-)
Imaging that you are dealing with something that looks like a tree and there are branches you don't want to be included so you cut them off.
Yes, attribute names are strings.

_keys = []
for _k in self._keys:
_ser = _k.to_dict()
Expand All @@ -761,16 +761,22 @@ def dump(self):

res = {
"keys": _keys,
"cache_time": self.cache_time,
"etag": self.etag,
"fileformat": self.fileformat,
"last_updated": self.last_updated,
"last_remote": self.last_remote,
"last_local": self.last_local,
"httpc_params": self.httpc_params,
"remote": self.remote,
"local": self.local,
"ignore_errors_period": self.ignore_errors_period,
"ignore_errors_until": self.ignore_errors_until,
"ignore_invalid_keys": self.ignore_invalid_keys,
"imp_jwks": self.imp_jwks,
"keytype": self.keytype,
"keyusage": self.keyusage,
"last_local": self.last_local,
"last_remote": self.last_remote,
"last_updated": self.last_updated,
"local": self.local,
"remote": self.remote,
"time_out": self.time_out,
"cache_time": self.cache_time,
}

if self.source:
Expand All @@ -782,17 +788,45 @@ def load(self, spec):
_keys = spec.get("keys", [])
if _keys:
self.do_keys(_keys)
self.source = spec.get("source", None)
self.cache_time = spec.get("cache_time", 0)
self.etag = spec.get("etag", "")
self.fileformat = spec.get("fileformat", "jwks")
self.last_updated = spec.get("last_updated", 0)
self.last_remote = spec.get("last_remote", None)
self.httpc_params = spec.get("httpc_params", {})
self.ignore_errors_period = spec.get("ignore_errors_period", 0)
self.ignore_errors_until = spec.get("ignore_errors_until", None)
self.ignore_invalid_keys = spec.get("ignore_invalid_keys", True)
self.imp_jwks = spec.get("imp_jwks", None)
self.keytype = (spec.get("keytype", "RSA"),)
self.keyusage = (spec.get("keyusage", None),)
self.last_local = spec.get("last_local", None)
self.remote = spec.get("remote", False)
self.last_remote = spec.get("last_remote", None)
self.last_updated = spec.get("last_updated", 0)
self.local = spec.get("local", False)
self.imp_jwks = spec.get("imp_jwks", None)
self.remote = spec.get("remote", False)
self.source = spec.get("source", None)
self.time_out = spec.get("time_out", 0)
self.cache_time = spec.get("cache_time", 0)
self.httpc_params = spec.get("httpc_params", {})
return self

def flush(self):
self._keys = []
self.cache_time = (300,)
self.etag = ""
self.fileformat = "jwks"
# self.httpc=None,
self.httpc_params = (None,)
self.ignore_errors_period = 0
self.ignore_errors_until = None
self.ignore_invalid_keys = True
self.imp_jwks = None
self.keytype = ("RSA",)
self.keyusage = (None,)
self.last_local = None # UNIX timestamp of last local update
self.last_remote = None # HTTP Date of last remote update
self.last_updated = 0
self.local = False
self.remote = False
self.source = None
self.time_out = 0
return self


Expand Down Expand Up @@ -1246,3 +1280,19 @@ def init_key(filename, type, kid="", **kwargs):
_new_key = key_gen(type, kid=kid, **kwargs)
dump_jwk(filename, _new_key)
return _new_key


def key_by_alg(alg: str):
if alg.startswith("RS"):
return key_gen("RSA", alg="RS256")
elif alg.startswith("ES"):
if alg == "ES256":
return key_gen("EC", crv="P-256")
elif alg == "ES384":
return key_gen("EC", crv="P-384")
elif alg == "ES512":
return key_gen("EC", crv="P-521")
elif alg.startswith("HS"):
return key_gen("sym")

raise ValueError("Don't know who to create a key to use with '{}'".format(alg))
21 changes: 18 additions & 3 deletions src/cryptojwt/key_issuer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging
import os
from typing import Optional

from requests import request

Expand Down Expand Up @@ -350,16 +351,18 @@ def __len__(self):
nr += len(kb)
return nr

def dump(self, exclude=None):
def dump(self, exclude=None, cutoff: Optional[list] = None) -> dict:
"""
Returns the content as a dictionary.

:param exclude: Issuer that should not be include in the dump
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • exclude_issuer (perhaps make it exclude_issuers and a Optional[List[str]] to allow multiple issuers to be excluded?)
  • exclude_attrs (or exclude_attributes)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK then.

:param cutoff: List of attribute name for objects that should be ignored.
:return: A dictionary
"""

_bundles = []
for kb in self._bundles:
_bundles.append(kb.dump())
_bundles.append(kb.dump(cutoff=cutoff))

info = {
"name": self.name,
Expand All @@ -375,7 +378,7 @@ def dump(self, exclude=None):
def load(self, info):
"""

:param items: A list with the information
:param items: A dictionary with the information to load
:return:
"""
self.name = info["name"]
Expand All @@ -387,6 +390,18 @@ def load(self, info):
self._bundles = [KeyBundle().load(val) for val in info["bundles"]]
return self

def flush(self):
self.ca_certs = (None,)
self.keybundle_cls = (KeyBundle,)
self.remove_after = (3600,)
self.httpc = (None,)
self.httpc_params = (None,)
self.name = ""
self.spec2key = None
self.remove_after = 0
self._bundles = []
return self

def update(self):
for kb in self._bundles:
kb.update()
Expand Down
72 changes: 45 additions & 27 deletions src/cryptojwt/key_jar.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def __init__(
remove_after=3600,
httpc=None,
httpc_params=None,
storage=None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we assume that no one (excluding closing family) has used the storage class yet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes !

):
"""
KeyJar init function
Expand All @@ -43,15 +42,9 @@ def __init__(
:param remove_after: How long keys marked as inactive will remain in the key Jar.
:param httpc: A HTTP client to use. Default is Requests request.
:param httpc_params: HTTP request parameters
:param storage: An instance that can store information. It basically look like dictionary.
:return: Keyjar instance
"""

if storage is None:
self._issuers = {}
else:
self._issuers = storage

self._issuers = {}
self.spec2key = {}
self.ca_certs = ca_certs
self.keybundle_cls = keybundle_cls
Expand Down Expand Up @@ -617,8 +610,6 @@ def copy(self):
"""
Make deep copy of the content of this key jar.

Note that if this key jar uses an external storage module the copy will not.

:return: A :py:class:`oidcmsg.key_jar.KeyJar` instance
"""

Expand All @@ -635,44 +626,74 @@ def copy(self):
def __len__(self):
return len(self._issuers)

def dump(self, exclude=None):
def dump(self, exclude: Optional[bool] = None, cutoff: Optional[list] = None) -> dict:
"""
Returns the key jar content as dictionary

:param cutoff: list of attribute names that should be ignored when dumping.
:type cutoff: list
:return: A dictionary
"""

info = {
"spec2key": self.spec2key,
"ca_certs": self.ca_certs,
"httpc_params": self.httpc_params,
"keybundle_cls": qualified_name(self.keybundle_cls),
"remove_after": self.remove_after,
"httpc_params": self.httpc_params,
"spec2key": self.spec2key,
}

_issuers = {}
for _id, _issuer in self._issuers.items():
if exclude and _issuer.name in exclude:
continue
_issuers[_id] = _issuer.dump()
_issuers[_id] = _issuer.dump(cutoff=cutoff)
info["issuers"] = _issuers

return info

def dumps(self, exclude=None):
"""
Returns a JSON representation of the key jar

:param exclude: Exclude these issuers
:return: A string
"""
_dict = self.dump(exclude=exclude)
return json.dumps(_dict)

def load(self, info):
"""

:param info: A dictionary with the information
:return:
"""
self.spec2key = info["spec2key"]
self.ca_certs = info["ca_certs"]
self.keybundle_cls = importer(info["keybundle_cls"])
self.remove_after = info["remove_after"]
self.httpc_params = info["httpc_params"]
self.ca_certs = info.get("ca_certs", None)
self.httpc_params = info.get("httpc_params", None)
self.keybundle_cls = importer(info.get("keybundle_cls", KeyBundle))
self.remove_after = info.get("remove_after", 3600)
self.spec2key = info.get("spec2key", {})

_issuers = info.get("issuers", None)
if _issuers is None:
self._issuers = {}
else:
for _issuer_id, _issuer_desc in _issuers.items():
self._issuers[_issuer_id] = KeyIssuer().load(_issuer_desc)
return self

def loads(self, string):
return self.load(json.loads(string))

def flush(self):
self.ca_certs = None
self.httpc_params = None
self._issuers = {}
self.keybundle_cls = KeyBundle
self.remove_after = 3600
self.spec2key = {}
# self.httpc=None,

for _issuer_id, _issuer_desc in info["issuers"].items():
self._issuers[_issuer_id] = KeyIssuer().load(_issuer_desc)
return self

@deprecated_alias(issuer="issuer_id", owner="issuer_id")
Expand Down Expand Up @@ -705,7 +726,7 @@ def rotate_keys(self, key_conf, kid_template="", issuer_id=""):
# =============================================================================


def build_keyjar(key_conf, kid_template="", keyjar=None, issuer_id="", storage=None):
def build_keyjar(key_conf, kid_template="", keyjar=None, issuer_id=""):
"""
Builds a :py:class:`oidcmsg.key_jar.KeyJar` instance or adds keys to
an existing KeyJar based on a key specification.
Expand Down Expand Up @@ -744,7 +765,6 @@ def build_keyjar(key_conf, kid_template="", keyjar=None, issuer_id="", storage=N
kid_template is given then the built-in function add_kid() will be used.
:param keyjar: If an KeyJar instance the new keys are added to this key jar.
:param issuer_id: The default owner of the keys in the key jar.
:param storage: A Storage instance.
:return: A KeyJar instance
"""

Expand All @@ -753,7 +773,7 @@ def build_keyjar(key_conf, kid_template="", keyjar=None, issuer_id="", storage=N
return None

if keyjar is None:
keyjar = KeyJar(storage=storage)
keyjar = KeyJar()

keyjar[issuer_id] = _issuer

Expand All @@ -767,7 +787,6 @@ def init_key_jar(
key_defs="",
issuer_id="",
read_only=True,
storage=None,
):
"""
A number of cases here:
Expand Down Expand Up @@ -805,7 +824,6 @@ def init_key_jar(
:param key_defs: A definition of what keys should be created if they are not already available
:param issuer_id: The owner of the keys
:param read_only: This function should not attempt to write anything to a file system.
:param storage: A Storage instance.
:return: An instantiated :py:class;`oidcmsg.key_jar.KeyJar` instance
"""

Expand All @@ -819,7 +837,7 @@ def init_key_jar(
if _issuer is None:
raise ValueError("Could not find any keys")

keyjar = KeyJar(storage=storage)
keyjar = KeyJar()
keyjar[issuer_id] = _issuer
return keyjar

Expand Down
Loading