-
Notifications
You must be signed in to change notification settings - Fork 17
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
Changes from 4 commits
8714a99
bdf512d
f518bcb
aaf9832
5467ca1
85c6a4a
161db5e
eba5299
cd543a9
1bf46d6
bbe0d1e
99f3780
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
import time | ||
from datetime import datetime | ||
from functools import cmp_to_key | ||
from typing import Optional | ||
|
||
import requests | ||
|
||
|
@@ -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 | ||
|
||
|
@@ -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 | ||
|
@@ -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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Somewhat confusing attribute name (at least for me). Perhaps The type should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Horticultural term :-) |
||
_keys = [] | ||
for _k in self._keys: | ||
_ser = _k.to_dict() | ||
|
@@ -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: | ||
|
@@ -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 | ||
|
||
|
||
|
@@ -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") | ||
jschlyter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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)) |
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 | ||
|
||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
|
@@ -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"] | ||
|
@@ -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() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,6 @@ def __init__( | |
remove_after=3600, | ||
httpc=None, | ||
httpc_params=None, | ||
storage=None, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes ! |
||
): | ||
""" | ||
KeyJar init function | ||
|
@@ -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 | ||
|
@@ -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 | ||
""" | ||
|
||
|
@@ -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") | ||
|
@@ -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. | ||
|
@@ -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 | ||
""" | ||
|
||
|
@@ -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 | ||
|
||
|
@@ -767,7 +787,6 @@ def init_key_jar( | |
key_defs="", | ||
issuer_id="", | ||
read_only=True, | ||
storage=None, | ||
): | ||
""" | ||
A number of cases here: | ||
|
@@ -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 | ||
""" | ||
|
||
|
@@ -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 | ||
|
||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK