Skip to content

Commit

Permalink
Merge pull request #1 from valohai/renovate
Browse files Browse the repository at this point in the history
Renovations
  • Loading branch information
akx authored Aug 26, 2024
2 parents bce6750 + 552151f commit d1e2421
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 50 deletions.
70 changes: 70 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: CI

on:
push:
branches: [master]
tags: ["v*"]
pull_request:
branches: [master]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies
run: pip install -e .[dev]
- name: Test with pytest
run: pytest -v --cov .
- uses: codecov/codecov-action@v4
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: pre-commit/[email protected]
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install build
- run: python -m build .
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist
publish:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
needs:
- build
name: Upload release to PyPI
runs-on: ubuntu-latest
environment:
name: release
url: https://pypi.org/p/oidckit/
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
verbose: true
print-hash: true
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
minimum_pre_commit_version: 2.15.0
ci:
autofix_prs: false
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.2
hooks:
- id: ruff
args:
- --fix
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Unobtrusive pluggable OpenID Connect consumer toolkit
Usage
-----

Construct a configuration class and a client class.
Construct a configuration class and a client class.
This example is for [Microsoft Identity Platform / Azure AD 2.0](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols) OIDC flows, based on [the published OIDC configuration](https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration).
For other providers, you may wish to also override some of the `Provider` functions.

Expand Down Expand Up @@ -62,7 +62,7 @@ Congratulations! If everything went fine, `auth_resp` will contain token data fr
You can use this – according to the instructions of the IDP, of course – to sign up an user, log them in, etc.

For instance, for MSIP, [`auth_resp.decode_id_token()['sub']` will contain an identifier](https://docs.microsoft.com/en-gb/azure/active-directory/develop/id-tokens)
uniquely identifying the user (for your application!).
uniquely identifying the user (for your application!).

Acknowledgements
----------------
Expand Down
9 changes: 6 additions & 3 deletions oidckit/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@


def decode_jws(
payload: bytes, key: dict, expected_algorithm: str, verify: bool = True
payload: bytes,
key: dict,
expected_algorithm: str,
verify: bool = True,
) -> dict:
jws = JWS.from_compact(payload)
if verify:
Expand All @@ -18,7 +21,7 @@ def decode_jws(

if alg != expected_algorithm:
raise OIDCError(
f"Algorithm mismatch: offered {alg} is not expected {expected_algorithm}"
f"Algorithm mismatch: offered {alg} is not expected {expected_algorithm}",
)

jwk = JWK.from_json(key)
Expand All @@ -40,7 +43,7 @@ def get_key_from_keyset_json(keyset_json: dict, token: bytes) -> dict:
jwk_alg = jwk.get("alg")
if jwk_alg and jwk_alg != expected_alg:
raise OIDCError(
f"kid {header.kid} has alg {jwk_alg}, was expecting {header.alg}"
f"kid {header.kid} has alg {jwk_alg}, was expecting {header.alg}",
)
return jwk
raise OIDCError(f"Keyset has no matching key for kid {expected_kid}.")
Expand Down
4 changes: 3 additions & 1 deletion oidckit/excs.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ def raise_from_status(cls, response: requests.Response):
response.raise_for_status()
except requests.HTTPError as he:
raise cls(
he.response.text, request=he.request, response=he.response
he.response.text,
request=he.request,
response=he.response,
) from he
9 changes: 6 additions & 3 deletions oidckit/objects.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, TYPE_CHECKING
from typing import TYPE_CHECKING, Optional

if TYPE_CHECKING:
from oidckit.provider import OIDCProvider
Expand Down Expand Up @@ -47,7 +47,8 @@ def get_user_info(self):
def decode_id_token(self) -> dict:
if not self._decoded_id_token:
self._decoded_id_token = self.provider.decode_token(
self.id_token, nonce=self.auth_state.nonce
self.id_token,
nonce=self.auth_state.nonce,
)
return self._decoded_id_token

Expand All @@ -64,7 +65,9 @@ def decode_access_token(self, verify: bool = True) -> dict:
"""
if not self._decoded_access_token:
self._decoded_access_token = self.provider.decode_token(
self.access_token, nonce=self.auth_state.nonce, verify=verify
self.access_token,
nonce=self.auth_state.nonce,
verify=verify,
)
return self._decoded_access_token

Expand Down
25 changes: 18 additions & 7 deletions oidckit/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

import requests

from oidckit.excs import OIDCError, RemoteError
from oidckit.crypto import decode_jws, get_key_from_keyset_json
from oidckit.objects import AuthenticationState, AuthenticationResult
from oidckit.excs import OIDCError, RemoteError
from oidckit.objects import AuthenticationResult, AuthenticationState


class OIDCProviderConfiguration:
Expand Down Expand Up @@ -40,7 +40,11 @@ def __exit__(self, exc_type, exc_val, exc_tb):
self.session.close()

def build_authentication_request_params(
self, *, redirect_uri: str, state: str, request=None
self,
*,
redirect_uri: str,
state: str,
request=None,
) -> dict:
return {
"client_id": self.config.rp_client_id,
Expand All @@ -51,7 +55,10 @@ def build_authentication_request_params(
}

def build_token_request_payload(
self, *, code: str, auth_state: AuthenticationState
self,
*,
code: str,
auth_state: AuthenticationState,
):
return {
"client_id": self.config.rp_client_id,
Expand All @@ -74,7 +81,8 @@ def retrieve_token_key(self, token) -> dict:
RemoteError.raise_from_status(response)
self._jwks_data = response.json()
return get_key_from_keyset_json(
keyset_json=self._jwks_data, token=token
keyset_json=self._jwks_data,
token=token,
)
raise NotImplementedError("No idea how to get token key – subclass, please")

Expand All @@ -87,7 +95,10 @@ def get_payload_data(self, token: bytes, key: dict, verify: bool = True):
)

def decode_token(
self, token: str, nonce: Optional[str] = None, verify: bool = True
self,
token: str,
nonce: Optional[str] = None,
verify: bool = True,
) -> dict:
token = str(token).encode("utf-8")
key = self.retrieve_token_key(token)
Expand All @@ -97,7 +108,7 @@ def decode_token(
token_nonce = payload.get("nonce")
if nonce != token_nonce:
raise OIDCError(
f"Token nonce mismatch – expected {nonce}, got {token_nonce}"
f"Token nonce mismatch – expected {nonce}, got {token_nonce}",
)
return payload

Expand Down
19 changes: 13 additions & 6 deletions oidckit/routines.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from oidckit.crypto import get_random_string
from oidckit.excs import OIDCError
from oidckit.objects import (
AuthenticationState,
AuthenticationResult,
AuthenticationRequest,
AuthenticationResult,
AuthenticationState,
)
from oidckit.provider import OIDCProvider

Expand All @@ -24,7 +24,9 @@ def build_authentication_request(
state = get_random_string(state_size)

params = provider.build_authentication_request_params(
request=request, redirect_uri=redirect_uri, state=state
request=request,
redirect_uri=redirect_uri,
state=state,
)
if nonce_size > 0:
nonce = get_random_string(nonce_size)
Expand All @@ -35,7 +37,9 @@ def build_authentication_request(
return AuthenticationRequest(
redirect_url=f"{provider.config.op_authorization_endpoint}?{(urlencode(params))}",
auth_state=AuthenticationState(
nonce=nonce, state=state, redirect_uri=redirect_uri
nonce=nonce,
state=state,
redirect_uri=redirect_uri,
),
)

Expand All @@ -58,11 +62,14 @@ def process_callback_data(
raise OIDCError("Unexpected state code.")

token_payload = provider.build_token_request_payload(
code=code, auth_state=auth_state
code=code,
auth_state=auth_state,
)
token = provider.retrieve_token(token_payload)
auth_result = AuthenticationResult(
provider=provider, auth_state=auth_state, token=token
provider=provider,
auth_state=auth_state,
token=token,
)
if verify:
auth_result.decode_id_token()
Expand Down
55 changes: 55 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "oidckit"
dynamic = ["version"]
description = "Unobtrusive pluggable OpenID Connect"
readme = "README.md"
license = "MIT"
requires-python = ">=3.10"
authors = [
{ name = "Valohai" },
]
maintainers = [
{ name = "Aarni Koskela", email = "[email protected]" },
]
dependencies = [
"josepy>=1.2.0",
"requests",
]

[project.optional-dependencies]
dev = [
"pytest",
"pytest-cov",
]

[project.urls]
Homepage = "https://github.com/valohai/oidckit"

[tool.hatch.version]
path = "oidckit/__init__.py"

[tool.hatch.build.targets.sdist]
include = [
"/oidckit",
]

[tool.ruff.lint]
ignore = [
"E501",
]
select = [
"B",
"COM",
"E",
"F",
"I",
"TID",
"W",
]

[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
2 changes: 0 additions & 2 deletions setup.cfg

This file was deleted.

26 changes: 0 additions & 26 deletions setup.py

This file was deleted.

Empty file added tests/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions tests/test_smoke.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def test_smoke():
import oidckit

assert oidckit.__version__ is not None

0 comments on commit d1e2421

Please sign in to comment.