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

Mostly changes to do with issues discovered during certification. #47

Merged
merged 29 commits into from
Nov 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7d617b9
Create pypi.yml
peppelinux Jun 28, 2021
0a1532f
Merge pull request #41 from IdentityPython/pypi
rohe Jun 29, 2021
71ff1e2
Update keyjar when needed.
rohe Sep 22, 2021
711c799
Sformat needed to be carried.
rohe Sep 25, 2021
d2656c4
list_deser should transform str into list of str.
rohe Sep 26, 2021
d803147
Bump version.
rohe Sep 26, 2021
00792c3
post_logout_redirect_uri is singleton.
rohe Oct 5, 2021
04edd35
post_logout_redirect_uris is a list.
rohe Oct 6, 2021
521bf2b
Right kind of token.
rohe Oct 6, 2021
7c4c628
Remove unused code.
rohe Oct 7, 2021
48186d6
post_logout_redirect_uri - singular.
rohe Oct 13, 2021
40fe88a
Merge pull request #42 from IdentityPython/reread_jwks_uri
rohe Oct 27, 2021
11b1332
Update pypi.yml
peppelinux Oct 27, 2021
e6f931c
chore: several improvements for setup and README files
peppelinux Oct 27, 2021
45cdcb1
Create requirements-dev.txt
peppelinux Oct 27, 2021
2408c7b
Create python-ci.yml
peppelinux Oct 27, 2021
b7831ae
fix: setup import os
peppelinux Oct 27, 2021
e0b2811
Merge branch 'develop' of https://github.com/IdentityPython/JWTConnec…
peppelinux Oct 27, 2021
5bd63e8
Delete .travis.yml
peppelinux Oct 27, 2021
b60a2be
Fix file permissions
c00kiemon5ter Oct 27, 2021
407e90e
Fix calculation of epoch without taking into account the timezone
c00kiemon5ter Oct 27, 2021
daf7d6e
Update python-ci.yml
peppelinux Oct 27, 2021
1e2ee16
Merge pull request #46 from IdentityPython/fix-file-permissions
rohe Oct 28, 2021
cf687d7
Merge pull request #45 from IdentityPython/fix-timestamp-tz
rohe Oct 28, 2021
0fe4952
Merge pull request #44 from IdentityPython/adieu-travis
rohe Oct 28, 2021
e5cebd0
Update time_util.py
peppelinux Nov 2, 2021
bb9bfc7
Replace yaml load to safe_load
peppelinux Nov 2, 2021
ccc0f48
Merge pull request #48 from IdentityPython/time_utils_err
peppelinux Nov 2, 2021
ae0ff89
v1.5.0
peppelinux Nov 2, 2021
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
35 changes: 35 additions & 0 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Publish Python distribution to PyPI
on:
release:
types:
- published

jobs:
build-n-publish:
name: Publish Python distribution to PyPI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Setup Python 3.8
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
59 changes: 59 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: oidcmsg

on:
push:
branches: [ master, develop ]
pull_request:
branches: [ master, develop ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
python-version:
- '3.7'
- '3.8'
- '3.9'

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
python setup.py install
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 src/oidcmsg --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 src/oidcmsg --max-line-length 120 --count --exit-zero --statistics

- name: Test with pytest
run: |
pytest --cov=oidcmsg tests/
- name: Bandit Security Scan
run: |
bandit --skip B105,B106,B107 -r src/oidcmsg/
#- name: Upload coverage to Codecov
#uses: codecov/codecov-action@v1
#with:
#token: ${{ secrets.CODECOV_TOKEN }}
#file: example/coverage.xml
#flags: unittests
#env_vars: OS,PYTHON
#name: codecov-umbrella
#fail_ci_if_error: true
#path_to_write_report: ./codecov_report.txt
27 changes: 0 additions & 27 deletions .travis.yml

This file was deleted.

Empty file modified CHANGELOG.md
100755 → 100644
Empty file.
Empty file modified LICENSE
100755 → 100644
Empty file.
Empty file modified Makefile
100755 → 100644
Empty file.
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# oidcmsg

![CI build](https://github.com/IdentityPython/oidcmsg/workflows/oidcmsg/badge.svg)
![pypi](https://img.shields.io/pypi/v/oidcmsg.svg)
[![Downloads](https://pepy.tech/badge/oidcmsg)](https://pepy.tech/project/oidcmsg)
[![Downloads](https://pepy.tech/badge/oidcmsg/week)](https://pepy.tech/project/oidcmsg)
![License](https://img.shields.io/badge/license-Apache%202-blue.svg)

Implementation of OIDC protocol messages.

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

+ verifies that all the required parameters are present and has a value
+ verifies that the parameter values are of the right type
+ verifies that if there is a list of permitted values, a parameter value is on
+ verifies that if there is a list of permitted values, a parameter value is on
that list.

and finally if the value is a signed and/or encrypted JWT this package
will perform the necessary decryption and signature verification.
will perform the necessary decryption and signature verification.


Also implements a **KeyJar** which keeps keys belonging to
Also implements a **KeyJar** which keeps keys belonging to
different owners. One owner may have many keys.
If some of these keys have a common origin, like described in a JWKS.
Such a set will be kept in a **keyBundle**.
Also implemented in this package.
Also implemented in this package.

Please read the [Official Documentation](https://oidcmsg.readthedocs.io/) for getting usage examples and further informations.
Empty file modified doc/Makefile
100755 → 100644
Empty file.
Empty file modified doc/conf.py
100755 → 100644
Empty file.
Empty file modified doc/index.rst
100755 → 100644
Empty file.
Empty file modified doc/make.bat
100755 → 100644
Empty file.
Empty file modified pylama.ini
100755 → 100644
Empty file.
7 changes: 7 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pytest>=6.2.2
pytest-black>=0.3.12
pytest-cov>=2.11.1
pytest-isort>=1.3.0
pytest-localserver>=0.5.0
flake8
bandit
6 changes: 6 additions & 0 deletions setup.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
import re
import sys

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

with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
README = readme.read()

setup(
name="oidcmsg",
version=version,
description="Python implementation of OAuth2 and OpenID Connect messages",
long_description=README,
long_description_content_type='text/markdown',
author="Roland Hedberg",
author_email="roland@catalogix.se",
license="Apache 2.0",
Expand Down
2 changes: 1 addition & 1 deletion src/oidcmsg/__init__.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__author__ = "Roland Hedberg"
__version__ = "1.4.0"
__version__ = "1.5.0"

import os
from typing import Dict
Expand Down
Empty file modified src/oidcmsg/exception.py
100755 → 100644
Empty file.
3 changes: 2 additions & 1 deletion src/oidcmsg/impexp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import List
from typing import Optional

from cryptojwt import as_unicode
from cryptojwt.utils import as_bytes
from cryptojwt.utils import importer
from cryptojwt.utils import qualified_name
Expand All @@ -25,7 +26,7 @@ def __init__(self):
def dump_attr(self, cls, item, exclude_attributes: Optional[List[str]] = None) -> dict:
if cls in [None, 0, "", [], {}, bool, b'']:
if cls == b'':
val = as_bytes(item)
val = as_unicode(item)
else:
val = item
elif cls == "DICT_TYPE":
Expand Down
2 changes: 1 addition & 1 deletion src/oidcmsg/logging.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def configure_logging(debug: Optional[bool] = False,
config_source = 'dictionary'
elif filename is not None and os.path.exists(filename):
with open(filename, "rt") as file:
config_dict = yaml.load(file)
config_dict = yaml.safe_load(file)
config_source = 'file'
else:
config_dict = LOGGING_DEFAULT
Expand Down
68 changes: 39 additions & 29 deletions src/oidcmsg/message.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -314,10 +314,10 @@ def from_dict(self, dictionary, **kwargs):
self._dict[key] = val
continue

self._add_value(skey, vtyp, key, val, _deser, null_allowed)
self._add_value(skey, vtyp, key, val, _deser, null_allowed, sformat="dict")
return self

def _add_value(self, skey, vtyp, key, val, _deser, null_allowed):
def _add_value(self, skey, vtyp, key, val, _deser, null_allowed, sformat="urlencoded"):
"""
Main method for adding a value to the instance. Does all the
checking on type of value and if among allowed values.
Expand Down Expand Up @@ -350,7 +350,7 @@ def _add_value(self, skey, vtyp, key, val, _deser, null_allowed):
self._dict[skey] = [val]
elif _deser:
try:
self._dict[skey] = _deser(val, sformat="urlencoded")
self._dict[skey] = _deser(val, sformat=sformat)
except Exception as exc:
raise DecodeError(ERRTXT % (key, exc))
else:
Expand Down Expand Up @@ -402,16 +402,6 @@ def _add_value(self, skey, vtyp, key, val, _deser, null_allowed):
except Exception as exc:
raise DecodeError(ERRTXT % (key, exc))
else:
# if isinstance(val, str):
# self._dict[skey] = val
# elif isinstance(val, list):
# if len(val) == 1:
# self._dict[skey] = val[0]
# elif not len(val):
# pass
# else:
# raise TooManyValues(key)
# else:
self._dict[skey] = val
elif vtyp is int:
try:
Expand Down Expand Up @@ -468,6 +458,28 @@ def to_jwt(self, key=None, algorithm="", lev=0, lifetime=0):
_jws = JWS(self.to_json(lev), alg=algorithm)
return _jws.sign_compact(key)

def _gather_keys(self, keyjar, jwt, header, **kwargs):
key = []

if keyjar:
_keys = keyjar.get_jwt_verify_keys(jwt, **kwargs)
if not _keys:
keyjar.update()
_keys = keyjar.get_jwt_verify_keys(jwt, **kwargs)
key.extend(_keys)

if "alg" in header and header["alg"] != "none":
if not key:
if keyjar:
keyjar.update()
key = keyjar.get_jwt_verify_keys(jwt, **kwargs)
if not key:
raise MissingSigningKey("alg=%s" % header["alg"])
else:
raise MissingSigningKey("alg=%s" % header["alg"])

return key

def from_jwt(self, txt, keyjar, verify=True, **kwargs):
"""
Given a signed and/or encrypted JWT, verify its correctness and then
Expand Down Expand Up @@ -515,7 +527,6 @@ def from_jwt(self, txt, keyjar, verify=True, **kwargs):
jso = _jwt.payload()
_header = _jwt.headers

key = []
# if "sender" in kwargs:
# key.extend(keyjar.get_verify_key(owner=kwargs["sender"]))

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

if "alg" in _header and _header["alg"] != "none":
if not key:
raise MissingSigningKey("alg=%s" % _header["alg"])
if not key:
raise MissingSigningKey("alg=%s" % _header["alg"])

logger.debug("Found signing key.")
try:
_verifier.verify_compact(txt, key)
except NoSuitableSigningKeys:
if keyjar:
keyjar.update()
key = keyjar.get_jwt_verify_keys(_jwt, **kwargs)
_verifier.verify_compact(txt, key)
_verifier.verify_compact(txt, key)

self.jws_header = _jwt.headers
else:
Expand Down Expand Up @@ -850,8 +853,12 @@ def add_non_standard(msg1, msg2):


def list_serializer(vals, sformat="urlencoded", lev=0):
if isinstance(vals, str) or not isinstance(vals, list):
if isinstance(vals, str) and sformat == "dict":
return [vals]

if not isinstance(vals, list):
raise ValueError("Expected list: %s" % vals)

if sformat == "urlencoded":
return " ".join(vals)
else:
Expand All @@ -864,8 +871,11 @@ def list_deserializer(val, sformat="urlencoded"):
return val.split(" ")
elif isinstance(val, list) and len(val) == 1:
return val[0].split(" ")
else:
return val
elif sformat == "dict":
if isinstance(val, str):
val = [val]

return val


def sp_sep_list_serializer(vals, sformat="urlencoded", lev=0):
Expand Down
Empty file modified src/oidcmsg/oauth2/__init__.py
100755 → 100644
Empty file.
12 changes: 2 additions & 10 deletions src/oidcmsg/oidc/__init__.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ class RegistrationRequest(Message):
# "client_id": SINGLE_OPTIONAL_STRING,
# "client_secret": SINGLE_OPTIONAL_STRING,
# "access_token": SINGLE_OPTIONAL_STRING,
"post_logout_redirect_uris": OPTIONAL_LIST_OF_STRINGS,
"post_logout_redirect_uri": SINGLE_OPTIONAL_STRING,
"frontchannel_logout_uri": SINGLE_OPTIONAL_STRING,
"frontchannel_logout_session_required": SINGLE_OPTIONAL_BOOLEAN,
"backchannel_logout_uri": SINGLE_OPTIONAL_STRING,
Expand Down Expand Up @@ -771,14 +771,6 @@ def pack(self, alg="", **kwargs):
else:
self.pack_init()

# if 'jti' in self.c_param:
# try:
# _jti = kwargs['jti']
# except KeyError:
# _jti = uuid.uuid4().hex
#
# self['jti'] = _jti

def to_jwt(self, key=None, algorithm="", lev=0, lifetime=0):
self.pack(alg=algorithm, lifetime=lifetime)
return Message.to_jwt(self, key=key, algorithm=algorithm, lev=lev)
Expand All @@ -797,7 +789,7 @@ def verify(self, **kwargs):
# check that I'm among the recipients
if kwargs["client_id"] not in self["aud"]:
raise NotForMe(
"{} not in aud:{}".format(kwargs["client_id"], self["aud"]), self
'"{}" not in {}'.format(kwargs["client_id"], self["aud"]), self
)

# Then azp has to be present and be one of the aud values
Expand Down
2 changes: 1 addition & 1 deletion src/oidcmsg/oidc/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,6 @@ def verify(self, **kwargs):
return False

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

return True
Loading