Skip to content

Commit

Permalink
Merge pull request #6 from robertbetts/develop
Browse files Browse the repository at this point in the history
Much happier that the configuration is flowing through the package co…
  • Loading branch information
robertbetts committed Jul 14, 2023
2 parents 0b4a3e6 + b4166a1 commit 8ae25a1
Show file tree
Hide file tree
Showing 20 changed files with 494 additions and 159 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ ID_SERVICE_BIND=${ID_SERVICE_BIND:-0.0.0.0}
ID_SERVICE_PORT_GW=${ID_SERVICE_PORT_GW:-8100}
ID_SERVICE_HOST_GW=${GATEWAY_HOST:-localhost}
CA_KEY_FILENAME=${CA_KEY_FILENAME:-certs/ca_key.pem}
CA_CERT_FILENAME=${CA_CERT_FILENAME:-certs/ca_cert.pem}
ORG_KEY_FILENAME=${ORG_KEY_FILENAME:-certs/key.pem}
ORG_CERT_FILENAME=${ORG_CERT_FILENAME:-certs/cert.pem}
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ exclude = [

[tool.pylint]
#ignore-paths = "tests"
notes = "FIXME,XXX"


[tool.poetry.dependencies]
python = "^3.10"
Expand Down
11 changes: 7 additions & 4 deletions src/openid_whisperer/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
import os
from uuid import uuid4
from typing import Type, Optional, Tuple, TextIO, Any, Dict, Iterable
from typing import Optional, Tuple, BinaryIO, Any

from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa
Expand All @@ -18,7 +18,10 @@
initialize_logging,
)

# Config pre-initialisation
_cached_config: Optional["Config"] = None
initialize_logging()
logging.getLogger("faker.factory").setLevel(logging.WARNING)


class ConfigurationException(Exception):
Expand Down Expand Up @@ -149,7 +152,7 @@ def load_config(self) -> None:
config_to_initialise.update(self.init_defaults)
for key, value in config_to_initialise.items():
func, default = value
env_var: str = os.environ.get(key.upper(), default)
env_var: Any = os.environ.get(key.upper(), default)
try:
key_value = func(env_var)
setattr(self, key, key_value)
Expand Down Expand Up @@ -177,8 +180,8 @@ def init_certs(self) -> None:
"""

def load_cert_pair(
cert_file: TextIO,
key_file: Optional[TextIO] = None,
cert_file: BinaryIO,
key_file: Optional[BinaryIO] = None,
key_password: Optional[str] = None,
) -> Tuple[x509.Certificate, Optional[rsa.RSAPrivateKey]]:
cert: x509.Certificate = x509.load_pem_x509_certificate(
Expand Down
16 changes: 8 additions & 8 deletions src/openid_whisperer/main.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
""" Module for initialising the OpenID Whisperer running service
"""
from flask import Flask
from openid_whisperer.utils.cert_utils import get_ssl_context
from openid_whisperer.openid_blueprint import openid_blueprint

from openid_whisperer.config import get_cached_config

config = get_cached_config()
config.init_logging()


def app() -> Flask:
def app() -> "Flask":
"""returns WSGI compliant Object wrapper for openid_whisperer"""
_ = get_cached_config() # this results in config initialised if not already.
flask_app = Flask(__name__)
from flask import Flask

flask_app = Flask("openid_whisperer")
flask_app.register_blueprint(openid_blueprint)
return flask_app


def main() -> None: # pragma: no cover
"""Main entrypoint for a standalone Python running instance"""
config = get_cached_config()
ca_certs = [config.ca_cert] if config.ca_cert else None

flask_app: Flask = app()
flask_app: "Flask" = app()
flask_app.run(
ssl_context=get_ssl_context(
certificate=config.org_cert,
Expand Down
26 changes: 13 additions & 13 deletions src/openid_whisperer/openid_blueprint.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
""" Flask Blueprint with OpenID compatible endpoints
"""
import logging
import json
from typing import Dict, Any, Type
from typing import Dict, Any

from flask import (
Blueprint,
Expand All @@ -25,16 +24,17 @@
from openid_whisperer.utils.token_store import (
TokenIssuerCertificateStoreException,
)
from openid_whisperer.utils.common import package_get_logger

logger = logging.getLogger(__name__)
logger = package_get_logger(__name__)


class UserInfoExtensionTemplate:
pass


def register_user_info_extension(
openid_api: Type[OpenidApiInterface],
openid_api: OpenidApiInterface,
extension: str | UserInfoExtensionTemplate | None = None,
) -> None:
"""Register an extension with the credential store that returns user_information claims
Expand Down Expand Up @@ -163,7 +163,7 @@ def authorize_get() -> ResponseReturnValue:
abort(403, str(e))

except Exception as e:
logging.exception(e)
logger.exception(e)
error = f"server_error: Error {request.method} {request.url} {e}"
abort(500, error)

Expand Down Expand Up @@ -233,7 +233,7 @@ def authorize_post() -> ResponseReturnValue:
status_code = 403

except Exception as e:
logging.exception(e)
logger.exception(e)
openid_response = {
"error_code": "server_error",
"error_description": f"Error {request.method} {request.url} {e}",
Expand Down Expand Up @@ -360,7 +360,7 @@ def token() -> ResponseReturnValue:
response = e.to_dict()
status_code = 403
except Exception as e:
logging.exception(e)
logger.exception(e)
response = {
"error_code": "server_error",
"error_description": f"Error {request.method} {request.url} {e}",
Expand Down Expand Up @@ -394,7 +394,7 @@ def userinfo() -> ResponseReturnValue:
response = e.to_dict()
status_code = 403
except Exception as e:
logging.exception(e)
logger.exception(e)
response = {
"error_code": "server_error",
"error_description": f"Error {request.method} {request.url} {e}",
Expand Down Expand Up @@ -454,7 +454,7 @@ def devicecode() -> ResponseReturnValue:
status_code = 403

except Exception as e:
logging.exception(e)
logger.exception(e)
response = {
"error_code": "server_error",
"error_description": f"Error {request.method} {request.url} {e}",
Expand Down Expand Up @@ -494,7 +494,7 @@ def get_logout() -> ResponseReturnValue:
status_code = 403

except Exception as e:
logging.exception(e)
logger.exception(e)
response = {
"error_code": "server_error",
"error_description": f"Error {request.method} {request.url} {e}",
Expand Down Expand Up @@ -528,7 +528,7 @@ def post_logout() -> ResponseReturnValue:
status_code = 403

except Exception as e:
logging.exception(e)
logger.exception(e)
response = {
"error_code": "server_error",
"error_description": f"Error {request.method} {request.url} {e}",
Expand All @@ -552,7 +552,7 @@ def keys() -> ResponseReturnValue:
response = e.to_dict()
status_code = 403
except Exception as e:
logging.exception(e)
logger.exception(e)
response = {
"error_code": "server_error",
"error_description": f"Error {request.method} {request.url} {e}",
Expand Down Expand Up @@ -585,7 +585,7 @@ def openid_configuration() -> ResponseReturnValue:
response = e.to_dict()
status_code = 403
except Exception as e: # pragma: no cover
logging.exception(e)
logger.exception(e)
response = {
"error_code": "server_error",
"error_description": f"Error {request.method} {request.url} {e}",
Expand Down
4 changes: 2 additions & 2 deletions src/openid_whisperer/openid_interface.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import logging
from typing import Dict, Any, Optional, List, Type
import datetime
import hashlib
Expand All @@ -18,8 +17,9 @@
from openid_whisperer.utils.credential_store import UserCredentialStore
from openid_whisperer.utils.token_store import TokenIssuerCertificateStore
from openid_whisperer.utils.user_info_ext import UserInfoExtensionTemplate
from openid_whisperer.utils.common import package_get_logger

logger = logging.getLogger(__name__)
logger = package_get_logger(__name__)

SCOPES_SUPPORTED = [
"user_impersonation",
Expand Down
4 changes: 2 additions & 2 deletions src/openid_whisperer/openid_types.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import TypedDict, Optional, Type

from openid_whisperer.openid_interface import OpenidApiInterfaceException
from openid_whisperer.utils.credential_store_utils import UserCredentialStoreException
from openid_whisperer.utils.token_store_utils import (
from openid_whisperer.utils.credential_store import UserCredentialStoreException
from openid_whisperer.utils.token_store import (
TokenIssuerCertificateStoreException,
)

Expand Down
13 changes: 5 additions & 8 deletions src/openid_whisperer/utils/cert_utils.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
""" Certificate Generation and Utility Functions
"""
import logging
import atexit
import os
import tempfile
from typing import Optional, List, Type, Callable
from typing import Optional, List
import ssl
from ssl import SSLContext

from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from openid_whisperer.utils.common import package_get_logger

logger = logging.getLogger(__name__)
logger = package_get_logger(__name__)


class CertUtilsException(Exception):
Expand Down Expand Up @@ -101,10 +99,9 @@ def dump_cert_and_ca_bundle(
ca_chain_filename = os.path.join(location, ca_chain_filename)

cert_data: bytes = certificate.public_bytes(encoding=serialization.Encoding.PEM)
ca_cert_data: bytes = b""
if ca_certificate:
ca_cert_data: bytes = ca_certificate.public_bytes(
encoding=serialization.Encoding.PEM
)
ca_cert_data = ca_certificate.public_bytes(encoding=serialization.Encoding.PEM)

if overwrite_existing_files is False and os.path.exists(cert_filename):
logger.warning("certificate exists, skipping. %s", cert_filename)
Expand Down
49 changes: 30 additions & 19 deletions src/openid_whisperer/utils/common.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
""" Module with package wide utility functions and constants.
"""

import base64
import hashlib
import logging
from calendar import timegm
from datetime import datetime, timezone
from typing import Dict, overload, List, Optional

LOGGER_NAME = "openid_whisperer"

SCOPE_PROFILES = [
"user_impersonation",
"offline_access",
"profile",
"email",
"address",
"phone",
"openid",
]
RESPONSE_TYPES_SUPPORTED: List[str] = [
Expand All @@ -32,25 +40,27 @@
"urn:ietf:params:oauth:grant-type:device_code",
"device_code",
]
SCOPE_PROFILE_CLAIMS = [
"name",
"family_name",
"given_name",
"middle_name",
"nickname",
"preferred_username",
"profile",
"picture",
"website",
"gender",
"birthdate",
"zoneinfo",
"locale",
"updated_at",
]
SCOPE_EMAIL_CLAIMS = ["email", "email_verified"]
SCOPE_ADDRESS_CLAIMS = ["address"]
SCOPE_PHONE_CLAIMS = ["phone_number", "phone_number_verified"]


def package_get_logger(name: str | None = None) -> logging.Logger:
"""Returns a logger as appropriate for the package, name is None then returns LOGGER_NAME
This function has been designed with the assumption that __name__ would be passed in when
called.
Assuming name will be a package.path.module_name, we only want to report in terms of the path not the
module name.
:param name:
:return:
"""
name = name if name else LOGGER_NAME
name_parts = name.split(".")
if len(name_parts) == 1:
logger_name = name
else:
logger_name = ".".join(name_parts[:-1])
logger_instance = logging.getLogger(logger_name)
return logger_instance


class GeneralPackageException(Exception):
Expand Down Expand Up @@ -147,6 +157,7 @@ def get_audience(
) -> List[str]:
audience: List[str] = [resource] if resource else []
audience.append(client_id)
scope = scope if scope else "openid"
for item in scope.split(" "):
aud = item.strip()
if item not in SCOPE_PROFILES and item != "":
Expand Down
Loading

0 comments on commit 8ae25a1

Please sign in to comment.