Skip to content
This repository was archived by the owner on Jun 1, 2023. It is now read-only.

Commit beaba7c

Browse files
authored
Merge pull request #47 from IdentityPython/develop
Mostly changes to do with issues discovered during certification.
2 parents 7a6cb5a + ae0ff89 commit beaba7c

29 files changed

+198
-82
lines changed

.github/workflows/pypi.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Publish Python distribution to PyPI
2+
on:
3+
release:
4+
types:
5+
- published
6+
7+
jobs:
8+
build-n-publish:
9+
name: Publish Python distribution to PyPI
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@master
13+
- name: Setup Python 3.8
14+
uses: actions/setup-python@v1
15+
with:
16+
python-version: 3.8
17+
- name: Install pypa/build
18+
run: >-
19+
python -m
20+
pip install
21+
build
22+
--user
23+
- name: Build a binary wheel and a source tarball
24+
run: >-
25+
python -m
26+
build
27+
--sdist
28+
--wheel
29+
--outdir dist/
30+
.
31+
- name: Publish distribution to PyPI
32+
uses: pypa/gh-action-pypi-publish@master
33+
with:
34+
user: __token__
35+
password: ${{ secrets.PYPI_API_TOKEN }}

.github/workflows/python-ci.yml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# This workflow will install Python dependencies, run tests and lint with a single version of Python
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3+
4+
name: oidcmsg
5+
6+
on:
7+
push:
8+
branches: [ master, develop ]
9+
pull_request:
10+
branches: [ master, develop ]
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
17+
strategy:
18+
fail-fast: false
19+
matrix:
20+
python-version:
21+
- '3.7'
22+
- '3.8'
23+
- '3.9'
24+
25+
steps:
26+
- uses: actions/checkout@v2
27+
- name: Set up Python ${{ matrix.python-version }}
28+
uses: actions/setup-python@v2
29+
with:
30+
python-version: ${{ matrix.python-version }}
31+
- name: Install dependencies
32+
run: |
33+
python -m pip install --upgrade pip
34+
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
35+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
36+
python setup.py install
37+
- name: Lint with flake8
38+
run: |
39+
# stop the build if there are Python syntax errors or undefined names
40+
flake8 src/oidcmsg --count --select=E9,F63,F7,F82 --show-source --statistics
41+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
42+
flake8 src/oidcmsg --max-line-length 120 --count --exit-zero --statistics
43+
44+
- name: Test with pytest
45+
run: |
46+
pytest --cov=oidcmsg tests/
47+
- name: Bandit Security Scan
48+
run: |
49+
bandit --skip B105,B106,B107 -r src/oidcmsg/
50+
#- name: Upload coverage to Codecov
51+
#uses: codecov/codecov-action@v1
52+
#with:
53+
#token: ${{ secrets.CODECOV_TOKEN }}
54+
#file: example/coverage.xml
55+
#flags: unittests
56+
#env_vars: OS,PYTHON
57+
#name: codecov-umbrella
58+
#fail_ci_if_error: true
59+
#path_to_write_report: ./codecov_report.txt

.travis.yml

Lines changed: 0 additions & 27 deletions
This file was deleted.

CHANGELOG.md

100755100644
File mode changed.

LICENSE

100755100644
File mode changed.

Makefile

100755100644
File mode changed.

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
# oidcmsg
2+
3+
![CI build](https://github.com/IdentityPython/oidcmsg/workflows/oidcmsg/badge.svg)
4+
![pypi](https://img.shields.io/pypi/v/oidcmsg.svg)
5+
[![Downloads](https://pepy.tech/badge/oidcmsg)](https://pepy.tech/project/oidcmsg)
6+
[![Downloads](https://pepy.tech/badge/oidcmsg/week)](https://pepy.tech/project/oidcmsg)
7+
![License](https://img.shields.io/badge/license-Apache%202-blue.svg)
8+
29
Implementation of OIDC protocol messages.
310

411
oidcmsg is the 2nd layer in the
@@ -10,17 +17,17 @@ It also does verification of messages , that is :
1017

1118
+ verifies that all the required parameters are present and has a value
1219
+ verifies that the parameter values are of the right type
13-
+ verifies that if there is a list of permitted values, a parameter value is on
20+
+ verifies that if there is a list of permitted values, a parameter value is on
1421
that list.
1522

1623
and finally if the value is a signed and/or encrypted JWT this package
17-
will perform the necessary decryption and signature verification.
24+
will perform the necessary decryption and signature verification.
1825

1926

20-
Also implements a **KeyJar** which keeps keys belonging to
27+
Also implements a **KeyJar** which keeps keys belonging to
2128
different owners. One owner may have many keys.
2229
If some of these keys have a common origin, like described in a JWKS.
2330
Such a set will be kept in a **keyBundle**.
24-
Also implemented in this package.
25-
31+
Also implemented in this package.
32+
2633
Please read the [Official Documentation](https://oidcmsg.readthedocs.io/) for getting usage examples and further informations.

doc/Makefile

100755100644
File mode changed.

doc/conf.py

100755100644
File mode changed.

doc/index.rst

100755100644
File mode changed.

doc/make.bat

100755100644
File mode changed.

pylama.ini

100755100644
File mode changed.

requirements-dev.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pytest>=6.2.2
2+
pytest-black>=0.3.12
3+
pytest-cov>=2.11.1
4+
pytest-isort>=1.3.0
5+
pytest-localserver>=0.5.0
6+
flake8
7+
bandit

setup.py

100755100644
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# See the License for the specific language governing permissions and
1515
# limitations under the License.
1616
#
17+
import os
1718
import re
1819
import sys
1920

@@ -43,10 +44,15 @@ def run_tests(self):
4344
version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]',
4445
fd.read(), re.MULTILINE).group(1)
4546

47+
with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
48+
README = readme.read()
49+
4650
setup(
4751
name="oidcmsg",
4852
version=version,
4953
description="Python implementation of OAuth2 and OpenID Connect messages",
54+
long_description=README,
55+
long_description_content_type='text/markdown',
5056
author="Roland Hedberg",
5157
author_email="[email protected]",
5258
license="Apache 2.0",

src/oidcmsg/__init__.py

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = "Roland Hedberg"
2-
__version__ = "1.4.0"
2+
__version__ = "1.5.0"
33

44
import os
55
from typing import Dict

src/oidcmsg/exception.py

100755100644
File mode changed.

src/oidcmsg/impexp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import List
33
from typing import Optional
44

5+
from cryptojwt import as_unicode
56
from cryptojwt.utils import as_bytes
67
from cryptojwt.utils import importer
78
from cryptojwt.utils import qualified_name
@@ -25,7 +26,7 @@ def __init__(self):
2526
def dump_attr(self, cls, item, exclude_attributes: Optional[List[str]] = None) -> dict:
2627
if cls in [None, 0, "", [], {}, bool, b'']:
2728
if cls == b'':
28-
val = as_bytes(item)
29+
val = as_unicode(item)
2930
else:
3031
val = item
3132
elif cls == "DICT_TYPE":

src/oidcmsg/logging.py

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def configure_logging(debug: Optional[bool] = False,
3838
config_source = 'dictionary'
3939
elif filename is not None and os.path.exists(filename):
4040
with open(filename, "rt") as file:
41-
config_dict = yaml.load(file)
41+
config_dict = yaml.safe_load(file)
4242
config_source = 'file'
4343
else:
4444
config_dict = LOGGING_DEFAULT

src/oidcmsg/message.py

100755100644
Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,10 @@ def from_dict(self, dictionary, **kwargs):
314314
self._dict[key] = val
315315
continue
316316

317-
self._add_value(skey, vtyp, key, val, _deser, null_allowed)
317+
self._add_value(skey, vtyp, key, val, _deser, null_allowed, sformat="dict")
318318
return self
319319

320-
def _add_value(self, skey, vtyp, key, val, _deser, null_allowed):
320+
def _add_value(self, skey, vtyp, key, val, _deser, null_allowed, sformat="urlencoded"):
321321
"""
322322
Main method for adding a value to the instance. Does all the
323323
checking on type of value and if among allowed values.
@@ -350,7 +350,7 @@ def _add_value(self, skey, vtyp, key, val, _deser, null_allowed):
350350
self._dict[skey] = [val]
351351
elif _deser:
352352
try:
353-
self._dict[skey] = _deser(val, sformat="urlencoded")
353+
self._dict[skey] = _deser(val, sformat=sformat)
354354
except Exception as exc:
355355
raise DecodeError(ERRTXT % (key, exc))
356356
else:
@@ -402,16 +402,6 @@ def _add_value(self, skey, vtyp, key, val, _deser, null_allowed):
402402
except Exception as exc:
403403
raise DecodeError(ERRTXT % (key, exc))
404404
else:
405-
# if isinstance(val, str):
406-
# self._dict[skey] = val
407-
# elif isinstance(val, list):
408-
# if len(val) == 1:
409-
# self._dict[skey] = val[0]
410-
# elif not len(val):
411-
# pass
412-
# else:
413-
# raise TooManyValues(key)
414-
# else:
415405
self._dict[skey] = val
416406
elif vtyp is int:
417407
try:
@@ -468,6 +458,28 @@ def to_jwt(self, key=None, algorithm="", lev=0, lifetime=0):
468458
_jws = JWS(self.to_json(lev), alg=algorithm)
469459
return _jws.sign_compact(key)
470460

461+
def _gather_keys(self, keyjar, jwt, header, **kwargs):
462+
key = []
463+
464+
if keyjar:
465+
_keys = keyjar.get_jwt_verify_keys(jwt, **kwargs)
466+
if not _keys:
467+
keyjar.update()
468+
_keys = keyjar.get_jwt_verify_keys(jwt, **kwargs)
469+
key.extend(_keys)
470+
471+
if "alg" in header and header["alg"] != "none":
472+
if not key:
473+
if keyjar:
474+
keyjar.update()
475+
key = keyjar.get_jwt_verify_keys(jwt, **kwargs)
476+
if not key:
477+
raise MissingSigningKey("alg=%s" % header["alg"])
478+
else:
479+
raise MissingSigningKey("alg=%s" % header["alg"])
480+
481+
return key
482+
471483
def from_jwt(self, txt, keyjar, verify=True, **kwargs):
472484
"""
473485
Given a signed and/or encrypted JWT, verify its correctness and then
@@ -515,7 +527,6 @@ def from_jwt(self, txt, keyjar, verify=True, **kwargs):
515527
jso = _jwt.payload()
516528
_header = _jwt.headers
517529

518-
key = []
519530
# if "sender" in kwargs:
520531
# key.extend(keyjar.get_verify_key(owner=kwargs["sender"]))
521532

@@ -524,21 +535,13 @@ def from_jwt(self, txt, keyjar, verify=True, **kwargs):
524535
if _header["alg"] == "none":
525536
pass
526537
elif verify:
527-
if keyjar:
528-
key.extend(keyjar.get_jwt_verify_keys(_jwt, **kwargs))
538+
key = self._gather_keys(keyjar, _jwt, _header, **kwargs)
529539

530-
if "alg" in _header and _header["alg"] != "none":
531-
if not key:
532-
raise MissingSigningKey("alg=%s" % _header["alg"])
540+
if not key:
541+
raise MissingSigningKey("alg=%s" % _header["alg"])
533542

534543
logger.debug("Found signing key.")
535-
try:
536-
_verifier.verify_compact(txt, key)
537-
except NoSuitableSigningKeys:
538-
if keyjar:
539-
keyjar.update()
540-
key = keyjar.get_jwt_verify_keys(_jwt, **kwargs)
541-
_verifier.verify_compact(txt, key)
544+
_verifier.verify_compact(txt, key)
542545

543546
self.jws_header = _jwt.headers
544547
else:
@@ -850,8 +853,12 @@ def add_non_standard(msg1, msg2):
850853

851854

852855
def list_serializer(vals, sformat="urlencoded", lev=0):
853-
if isinstance(vals, str) or not isinstance(vals, list):
856+
if isinstance(vals, str) and sformat == "dict":
857+
return [vals]
858+
859+
if not isinstance(vals, list):
854860
raise ValueError("Expected list: %s" % vals)
861+
855862
if sformat == "urlencoded":
856863
return " ".join(vals)
857864
else:
@@ -864,8 +871,11 @@ def list_deserializer(val, sformat="urlencoded"):
864871
return val.split(" ")
865872
elif isinstance(val, list) and len(val) == 1:
866873
return val[0].split(" ")
867-
else:
868-
return val
874+
elif sformat == "dict":
875+
if isinstance(val, str):
876+
val = [val]
877+
878+
return val
869879

870880

871881
def sp_sep_list_serializer(vals, sformat="urlencoded", lev=0):

src/oidcmsg/oauth2/__init__.py

100755100644
File mode changed.

src/oidcmsg/oidc/__init__.py

100755100644
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ class RegistrationRequest(Message):
633633
# "client_id": SINGLE_OPTIONAL_STRING,
634634
# "client_secret": SINGLE_OPTIONAL_STRING,
635635
# "access_token": SINGLE_OPTIONAL_STRING,
636-
"post_logout_redirect_uris": OPTIONAL_LIST_OF_STRINGS,
636+
"post_logout_redirect_uri": SINGLE_OPTIONAL_STRING,
637637
"frontchannel_logout_uri": SINGLE_OPTIONAL_STRING,
638638
"frontchannel_logout_session_required": SINGLE_OPTIONAL_BOOLEAN,
639639
"backchannel_logout_uri": SINGLE_OPTIONAL_STRING,
@@ -771,14 +771,6 @@ def pack(self, alg="", **kwargs):
771771
else:
772772
self.pack_init()
773773

774-
# if 'jti' in self.c_param:
775-
# try:
776-
# _jti = kwargs['jti']
777-
# except KeyError:
778-
# _jti = uuid.uuid4().hex
779-
#
780-
# self['jti'] = _jti
781-
782774
def to_jwt(self, key=None, algorithm="", lev=0, lifetime=0):
783775
self.pack(alg=algorithm, lifetime=lifetime)
784776
return Message.to_jwt(self, key=key, algorithm=algorithm, lev=lev)
@@ -797,7 +789,7 @@ def verify(self, **kwargs):
797789
# check that I'm among the recipients
798790
if kwargs["client_id"] not in self["aud"]:
799791
raise NotForMe(
800-
"{} not in aud:{}".format(kwargs["client_id"], self["aud"]), self
792+
'"{}" not in {}'.format(kwargs["client_id"], self["aud"]), self
801793
)
802794

803795
# Then azp has to be present and be one of the aud values

src/oidcmsg/oidc/session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,6 @@ def verify(self, **kwargs):
179179
return False
180180

181181
self[verified_claim_name("logout_token")] = idt
182-
logger.info("Verified ID Token: {}".format(idt.to_dict()))
182+
logger.info("Verified Logout Token: {}".format(idt.to_dict()))
183183

184184
return True

0 commit comments

Comments
 (0)