From 06e737199e42ac27608f23dffc59524866edf1cd Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 21:38:19 -0500 Subject: [PATCH 01/24] Use gh actions. --- .github/workflows/checks.yml | 29 +++++++++++++++++++++++++++ .github/workflows/lint.yml | 34 +++++++++++++++++++++++++++++++ .github/workflows/tests.yml | 39 ++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 000000000..31568dcd8 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,29 @@ +name: checks + +on: + push: + branches: [master] + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + poetry-check: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install Poetry + run: pipx install poetry==2.4.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.x' + - name: Validate pyproject.toml + run: poetry check + - name: Validate poetry.lock is in sync + run: poetry check --lock diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..30afb1569 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,34 @@ +name: lint + +on: + push: + branches: [master] + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install Poetry + run: pipx install poetry==2.4.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.x' + cache: poetry + - name: Install dev dependencies + run: poetry install --with dev + - name: black + run: poetry run black --check src/ tests/ example/ + - name: isort + run: poetry run isort --check-only src/ tests/ example/ + - name: flake8 + run: poetry run flake8 src/ diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..2e3a68d02 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,39 @@ +name: tests + +on: + push: + branches: [master] + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install xmlsec1 + run: | + sudo apt-get update + sudo apt-get install -y xmlsec1 + xmlsec1 --version + - name: Install Poetry + run: pipx install poetry==2.4.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ matrix.python-version }} + cache: poetry + - name: Install dependencies + run: poetry install --with test,coverage + - name: Run tests + run: poetry run pytest --import-mode=importlib --cov=saml2 --cov-report=term-missing From e3c6f2e8497ce41f070da73fa5760c4beaff972c Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 21:57:19 -0500 Subject: [PATCH 02/24] format: isort --- example/idp2_repoze/modules/login.mako.py | 4 +++- example/idp2_repoze/modules/root.mako.py | 5 ++++- src/saml2/cert.py | 4 ++-- src/saml2/mongo_store.py | 4 ++-- src/saml2/pack.py | 1 - src/saml2/server.py | 2 +- src/saml2/sigver.py | 6 +++--- src/saml2/time_util.py | 6 +++--- tests/test_37_entity_categories.py | 3 +-- tests/test_41_response.py | 2 +- tests/test_schema_validator.py | 1 - 11 files changed, 20 insertions(+), 18 deletions(-) diff --git a/example/idp2_repoze/modules/login.mako.py b/example/idp2_repoze/modules/login.mako.py index 211fa37f1..dc6d097ef 100644 --- a/example/idp2_repoze/modules/login.mako.py +++ b/example/idp2_repoze/modules/login.mako.py @@ -1,4 +1,6 @@ -from mako import cache, runtime +from mako import cache +from mako import runtime + UNDEFINED = runtime.UNDEFINED __M_dict_builtin = dict diff --git a/example/idp2_repoze/modules/root.mako.py b/example/idp2_repoze/modules/root.mako.py index 411992323..caf7f302c 100644 --- a/example/idp2_repoze/modules/root.mako.py +++ b/example/idp2_repoze/modules/root.mako.py @@ -1,4 +1,7 @@ -from mako import runtime, filters, cache +from mako import cache +from mako import filters +from mako import runtime + UNDEFINED = runtime.UNDEFINED __M_dict_builtin = dict diff --git a/src/saml2/cert.py b/src/saml2/cert.py index e90651e44..e9dd98684 100644 --- a/src/saml2/cert.py +++ b/src/saml2/cert.py @@ -1,10 +1,10 @@ __author__ = "haho0032" import base64 -from os import remove -from os.path import join from datetime import datetime from datetime import timezone +from os import remove +from os.path import join from OpenSSL import crypto import dateutil.parser diff --git a/src/saml2/mongo_store.py b/src/saml2/mongo_store.py index 44175c82f..cb198983f 100644 --- a/src/saml2/mongo_store.py +++ b/src/saml2/mongo_store.py @@ -1,7 +1,7 @@ -import logging -from hashlib import sha1 from datetime import datetime from datetime import timezone +from hashlib import sha1 +import logging from pymongo import MongoClient import pymongo.errors diff --git a/src/saml2/pack.py b/src/saml2/pack.py index cee1cf1c8..143ac7a3b 100644 --- a/src/saml2/pack.py +++ b/src/saml2/pack.py @@ -10,7 +10,6 @@ import base64 import html import logging - from urllib.parse import urlencode from urllib.parse import urlparse from xml.etree import ElementTree as ElementTree diff --git a/src/saml2/server.py b/src/saml2/server.py index ca2b312d7..ee733d45b 100644 --- a/src/saml2/server.py +++ b/src/saml2/server.py @@ -4,11 +4,11 @@ """Contains classes and functions that a SAML2.0 Identity provider (IdP) or attribute authority (AA) may use to conclude its tasks. """ +from dbm import error as DbmError import importlib import logging import shelve import threading -from dbm import error as DbmError from saml2 import BINDING_HTTP_REDIRECT from saml2 import class_name diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py index 738ac04b1..0f5111715 100644 --- a/src/saml2/sigver.py +++ b/src/saml2/sigver.py @@ -3,14 +3,14 @@ """ import base64 +from datetime import datetime +from datetime import timezone +from importlib.resources import files as _resource_files import hashlib import itertools import logging import os import re -from datetime import datetime -from datetime import timezone -from importlib.resources import files as _resource_files from subprocess import PIPE from subprocess import Popen from tempfile import NamedTemporaryFile diff --git a/src/saml2/time_util.py b/src/saml2/time_util.py index 86ad711d8..1a2475daf 100644 --- a/src/saml2/time_util.py +++ b/src/saml2/time_util.py @@ -6,12 +6,12 @@ """ import calendar +from datetime import datetime +from datetime import timedelta +from datetime import timezone import re import sys import time -from datetime import datetime -from datetime import timezone -from datetime import timedelta TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" diff --git a/tests/test_37_entity_categories.py b/tests/test_37_entity_categories.py index 894b03cf3..b1e93f2cb 100644 --- a/tests/test_37_entity_categories.py +++ b/tests/test_37_entity_categories.py @@ -1,8 +1,7 @@ from contextlib import closing -import pytest - from pathutils import full_path +import pytest from saml2 import config from saml2 import sigver diff --git a/tests/test_41_response.py b/tests/test_41_response.py index e6f26400a..ee4b1387b 100644 --- a/tests/test_41_response.py +++ b/tests/test_41_response.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -import logging from contextlib import closing from datetime import datetime from datetime import timezone +import logging from unittest.mock import Mock from unittest.mock import patch diff --git a/tests/test_schema_validator.py b/tests/test_schema_validator.py index ee1335832..49ad0a77a 100644 --- a/tests/test_schema_validator.py +++ b/tests/test_schema_validator.py @@ -1,5 +1,4 @@ from pathutils import full_path as expand_full_path - from pytest import mark from pytest import raises From cdc79afad62cd1f1cb8d40afbdc3c4c29a05d595 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 21:58:40 -0500 Subject: [PATCH 03/24] format: black --- src/saml2/__init__.py | 24 ++++++++++++------------ src/saml2/assertion.py | 10 +++++----- src/saml2/cert.py | 1 - src/saml2/client_base.py | 12 ++++++------ src/saml2/httputil.py | 2 +- src/saml2/mcache.py | 2 +- src/saml2/mdstore.py | 8 +++----- src/saml2/metadata.py | 10 +++++----- src/saml2/saml.py | 14 +++++++++----- src/saml2/sigver.py | 13 +++++++------ src/saml2/tools/parse_xsd2.py | 2 +- src/saml2/validate.py | 4 ++-- tests/attribute_statement_data.py | 2 +- tests/test_41_response.py | 2 +- tests/test_75_mongodb.py | 2 +- 15 files changed, 55 insertions(+), 53 deletions(-) diff --git a/src/saml2/__init__.py b/src/saml2/__init__.py index 01067b4be..ae1381d25 100644 --- a/src/saml2/__init__.py +++ b/src/saml2/__init__.py @@ -2,18 +2,18 @@ """Contains base classes representing SAML elements. - These codes were originally written by Jeffrey Scudder for - representing Saml elements. Takashi Matsuo had added some codes, and - changed some. Roland Hedberg rewrote the whole thing from bottom up so - barely anything but the original structures remained. - - Module objective: provide data classes for SAML constructs. These - classes hide the XML-ness of SAML and provide a set of native Python - classes to interact with. - - Conversions to and from XML should only be necessary when the SAML classes - "touch the wire" and are sent over HTTP. For this reason this module - provides methods and functions to convert SAML classes to and from strings. +These codes were originally written by Jeffrey Scudder for +representing Saml elements. Takashi Matsuo had added some codes, and +changed some. Roland Hedberg rewrote the whole thing from bottom up so +barely anything but the original structures remained. + +Module objective: provide data classes for SAML constructs. These +classes hide the XML-ness of SAML and provide a set of native Python +classes to interact with. + +Conversions to and from XML should only be necessary when the SAML classes +"touch the wire" and are sent over HTTP. For this reason this module +provides methods and functions to convert SAML classes to and from strings. """ import logging diff --git a/src/saml2/assertion.py b/src/saml2/assertion.py index 4c0ab1511..329d0b3e7 100644 --- a/src/saml2/assertion.py +++ b/src/saml2/assertion.py @@ -343,11 +343,11 @@ def get(self, attribute, sp_entity_id, default=None): restrictions = ( sp_restrictions if sp_restrictions is not None - else ra_restrictions - if ra_restrictions is not None - else default_restrictions - if default_restrictions is not None - else {} + else ( + ra_restrictions + if ra_restrictions is not None + else default_restrictions if default_restrictions is not None else {} + ) ) attribute_restriction = restrictions.get(attribute) diff --git a/src/saml2/cert.py b/src/saml2/cert.py index e9dd98684..3eaa18b7f 100644 --- a/src/saml2/cert.py +++ b/src/saml2/cert.py @@ -204,7 +204,6 @@ def create_cert_signed_certificate( sn=1, passphrase=None, ): - """ Will sign a certificate request with a give certificate. :param sign_cert_str: This certificate will be used to sign with. diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py index d5e797d76..388515bd4 100644 --- a/src/saml2/client_base.py +++ b/src/saml2/client_base.py @@ -391,17 +391,17 @@ def create_authn_request( None # SAML 2.0 errata says AllowCreate MUST NOT be used for transient ids if nameid_policy_format == NAMEID_FORMAT_TRANSIENT - else allow_create - if allow_create - else str(bool(allow_create_config)).lower() + else allow_create if allow_create else str(bool(allow_create_config)).lower() ) name_id_policy = ( kwargs.pop("name_id_policy", None) if "name_id_policy" in kwargs - else None - if not nameid_policy_format - else samlp.NameIDPolicy(allow_create=allow_create, format=nameid_policy_format) + else ( + None + if not nameid_policy_format + else samlp.NameIDPolicy(allow_create=allow_create, format=nameid_policy_format) + ) ) if name_id_policy and vorg: diff --git a/src/saml2/httputil.py b/src/saml2/httputil.py index c7abc6fdd..aa54d160d 100644 --- a/src/saml2/httputil.py +++ b/src/saml2/httputil.py @@ -183,7 +183,7 @@ def extract(environ, empty=False, err=False): """ input_stream = environ["wsgi.input"] content_length = int(environ.get("CONTENT_LENGTH", 0)) - input_data = input_stream.read(content_length).decode('utf-8') + input_data = input_stream.read(content_length).decode("utf-8") formdata = parse_qs(input_data) # Remove single entries from lists for key, value in iter(formdata.items()): diff --git a/src/saml2/mcache.py b/src/saml2/mcache.py index c464cfc29..dad33b398 100644 --- a/src/saml2/mcache.py +++ b/src/saml2/mcache.py @@ -57,7 +57,7 @@ def get_identity(self, subject_id, entities=None): res = {} oldees = [] - for (entity_id, item) in self._cache.get_multi(entities, f"{subject_id}_").items(): + for entity_id, item in self._cache.get_multi(entities, f"{subject_id}_").items(): try: info = self.get_info(item) except TooOld: diff --git a/src/saml2/mdstore.py b/src/saml2/mdstore.py index 2ea9742f0..202f8bd8a 100644 --- a/src/saml2/mdstore.py +++ b/src/saml2/mdstore.py @@ -207,7 +207,7 @@ def attribute_requirement(entity_descriptor, index=None): if index is not None and acs["index"] != index: continue - for attr in (acs.get("requested_attribute") or []): + for attr in acs.get("requested_attribute") or []: if attr.get("is_required") == "true": res["required"].append(attr) else: @@ -607,9 +607,7 @@ def parse(self, xmlstr): _md_desc = ( f"metadata file: {self.filename}" if isinstance(self, MetaDataFile) - else f"remote metadata: {self.url}" - if isinstance(self, MetaDataExtern) - else "metadata" + else f"remote metadata: {self.url}" if isinstance(self, MetaDataExtern) else "metadata" ) raise SAMLError(f"Failed to parse {_md_desc}") from e @@ -1327,7 +1325,7 @@ def subject_id_requirement(self, entity_id): "name_format": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "friendly_name": "subject-id", "is_required": "true", - } + }, ] elif subject_id_req == "pairwise-id": return [ diff --git a/src/saml2/metadata.py b/src/saml2/metadata.py index 3961c869f..02154bdca 100644 --- a/src/saml2/metadata.py +++ b/src/saml2/metadata.py @@ -493,7 +493,7 @@ def do_spsso_descriptor(conf, cert=None, enc_cert=None): endps = conf.getattr("endpoints", "sp") if endps: - for (endpoint, instlist) in do_endpoints(endps, ENDPOINTS["sp"]).items(): + for endpoint, instlist in do_endpoints(endps, ENDPOINTS["sp"]).items(): setattr(spsso, endpoint, instlist) ext = do_endpoints(endps, ENDPOINT_EXT["sp"]) @@ -547,7 +547,7 @@ def do_idpsso_descriptor(conf, cert=None, enc_cert=None): endps = conf.getattr("endpoints", "idp") if endps: - for (endpoint, instlist) in do_endpoints(endps, ENDPOINTS["idp"]).items(): + for endpoint, instlist in do_endpoints(endps, ENDPOINTS["idp"]).items(): setattr(idpsso, endpoint, instlist) _do_nameid_format(idpsso, conf, "idp") @@ -608,7 +608,7 @@ def do_aa_descriptor(conf, cert=None, enc_cert=None): endps = conf.getattr("endpoints", "aa") if endps: - for (endpoint, instlist) in do_endpoints(endps, ENDPOINTS["aa"]).items(): + for endpoint, instlist in do_endpoints(endps, ENDPOINTS["aa"]).items(): setattr(aad, endpoint, instlist) _do_nameid_format(aad, conf, "aa") @@ -647,7 +647,7 @@ def do_aq_descriptor(conf, cert=None, enc_cert=None): endps = conf.getattr("endpoints", "aq") if endps: - for (endpoint, instlist) in do_endpoints(endps, ENDPOINTS["aq"]).items(): + for endpoint, instlist in do_endpoints(endps, ENDPOINTS["aq"]).items(): setattr(aqs, endpoint, instlist) _do_nameid_format(aqs, conf, "aq") @@ -678,7 +678,7 @@ def do_pdp_descriptor(conf, cert=None, enc_cert=None): endps = conf.getattr("endpoints", "pdp") if endps: - for (endpoint, instlist) in do_endpoints(endps, ENDPOINTS["pdp"]).items(): + for endpoint, instlist in do_endpoints(endps, ENDPOINTS["pdp"]).items(): setattr(pdp, endpoint, instlist) _do_nameid_format(pdp, conf, "pdp") diff --git a/src/saml2/saml.py b/src/saml2/saml.py index 1c01dc16c..2e496d4a9 100644 --- a/src/saml2/saml.py +++ b/src/saml2/saml.py @@ -314,11 +314,15 @@ def _wrong_type_value(xsd, value): xsd_ns, xsd_type = ( ["", type(None)] if xsd_string is None - else ["", ""] - if xsd_string == "" - else [XSD if xsd_string in xsd_types_props else "", xsd_string] - if ":" not in xsd_string - else xsd_string.split(":", 1) + else ( + ["", ""] + if xsd_string == "" + else ( + [XSD if xsd_string in xsd_types_props else "", xsd_string] + if ":" not in xsd_string + else xsd_string.split(":", 1) + ) + ) ) xsd_type_props = xsd_types_props.get(xsd_type) diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py index 0f5111715..565d7a6ac 100644 --- a/src/saml2/sigver.py +++ b/src/saml2/sigver.py @@ -1,4 +1,4 @@ -""" Functions connected to signing and verifying. +"""Functions connected to signing and verifying. Based on the use of xmlsec1 binaries and not the python xmlsec module. """ @@ -317,7 +317,7 @@ def signed_instance_factory(instance, seccont, elements_to_sign=None): if not isinstance(instance, str): signed_xml = instance.to_string() - for (node_name, nodeid) in elements_to_sign: + for node_name, nodeid in elements_to_sign: signed_xml = seccont.sign_statement(signed_xml, node_name=node_name, node_id=nodeid) return signed_xml @@ -476,9 +476,9 @@ def parse_xmlsec_verify_output(output, version=None): raise XmlsecError(output) else: for line in output.splitlines(): - if line == 'Verification status: OK': + if line == "Verification status: OK": return True - elif line == 'Verification status: FAILED': + elif line == "Verification status: FAILED": raise XmlsecError(output) raise XmlsecError(output) @@ -844,7 +844,7 @@ def _run_xmlsec(self, com_list, extra_args): with NamedTemporaryFile(suffix=".xml") as ntf: com_list.extend(["--output", ntf.name]) if self.version_nums >= (1, 3): - com_list.extend(['--lax-key-search']) + com_list.extend(["--lax-key-search"]) com_list += extra_args logger.debug("xmlsec command: %s", " ".join(com_list)) @@ -883,6 +883,7 @@ def __init__(self): def version(self): try: import xmlsec + return xmlsec.__version__ except (ImportError, AttributeError): return "0.0.0" @@ -1723,7 +1724,7 @@ def multiple_signatures(self, statement, to_sign, key=None, key_file=None, sign_ :param key_file: A file that contains the key to be used :return: A possibly multiple signed statement """ - for (item, sid) in to_sign: + for item, sid in to_sign: if not sid: if not item.id: sid = item.id = sid() diff --git a/src/saml2/tools/parse_xsd2.py b/src/saml2/tools/parse_xsd2.py index 5129dd349..db358c260 100644 --- a/src/saml2/tools/parse_xsd2.py +++ b/src/saml2/tools/parse_xsd2.py @@ -1908,7 +1908,7 @@ def out(self): ignore = [p.name for (p, _l, _s) in tups] done = output(objekt, self.target_namespace, eldict, ignore) if done: - for (prop, lines, _) in tups: + for prop, lines, _ in tups: exceptions.extend(lines) block = [] else: diff --git a/src/saml2/validate.py b/src/saml2/validate.py index 51a41b992..5fd7ea5d9 100644 --- a/src/saml2/validate.py +++ b/src/saml2/validate.py @@ -354,7 +354,7 @@ def valid_instance(instance): except NotValid as exc: raise NotValid(f"Class '{class_name}' instance: {exc.args[0]}") - for (name, typ, required) in instclass.c_attributes.values(): + for name, typ, required in instclass.c_attributes.values(): value = getattr(instance, name, "") if required and not value: txt = f"Required value on property '{name}' missing" @@ -375,7 +375,7 @@ def valid_instance(instance): txt = ERROR_TEXT % (value, name, exc.args[0]) raise NotValid(f"Class '{class_name}' instance: {txt}") - for (name, _spec) in instclass.c_children.values(): + for name, _spec in instclass.c_children.values(): value = getattr(instance, name, "") try: diff --git a/tests/attribute_statement_data.py b/tests/attribute_statement_data.py index e15bcdd20..d98314457 100644 --- a/tests/attribute_statement_data.py +++ b/tests/attribute_statement_data.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -"""Testdata for attribute converters """ +"""Testdata for attribute converters""" STATEMENT1 = """ diff --git a/tests/test_41_response.py b/tests/test_41_response.py index ee4b1387b..90c56dd2e 100644 --- a/tests/test_41_response.py +++ b/tests/test_41_response.py @@ -147,7 +147,7 @@ def test_false_sign(self, mock_datetime, caplog): assert isinstance(resp, AuthnResponse) with raises(SignatureError): resp.verify() - assert 'The signature on the assertion cannot be verified.' in caplog.text + assert "The signature on the assertion cannot be verified." in caplog.text def test_other_response(self): with open(full_path("attribute_response.xml")) as fp: diff --git a/tests/test_75_mongodb.py b/tests/test_75_mongodb.py index 51ef67c9f..603745758 100644 --- a/tests/test_75_mongodb.py +++ b/tests/test_75_mongodb.py @@ -48,7 +48,7 @@ def test_flow(): }, userid="jeter", authn=AUTHN, - **rinfo + **rinfo, ) # What's stored away is the assertion From 1cabb4b2681ba95b58eb0cddbf1f013f74714a13 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 22:11:34 -0500 Subject: [PATCH 04/24] Pin Python to 3.12 to ensure flake8 will run. --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 30afb1569..3dc102ae0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,7 @@ jobs: run: pipx install poetry==2.4.0 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: '3.x' + python-version: '3.12' cache: poetry - name: Install dev dependencies run: poetry install --with dev From efdef18d212a2a759eee12a1494d32e3524c299d Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 22:16:26 -0500 Subject: [PATCH 05/24] Allow flake8 to fail for now. --- .github/workflows/lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 3dc102ae0..111a92929 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,4 +31,5 @@ jobs: - name: isort run: poetry run isort --check-only src/ tests/ example/ - name: flake8 + continue-on-error: true run: poetry run flake8 src/ From f3010abf5eb4a3cce3e944acb4526c30bdd1f959 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 22:23:00 -0500 Subject: [PATCH 06/24] Add setuptools==81.0.0 so flake8 will run. --- poetry.lock | 20 ++++++++++---------- pyproject.toml | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/poetry.lock b/poetry.lock index fdae3665a..8c68e1d9d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "alabaster" @@ -1535,25 +1535,25 @@ tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asy [[package]] name = "setuptools" -version = "80.9.0" +version = "81.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = true +optional = false python-versions = ">=3.9" -groups = ["main"] -markers = "extra == \"s2repoze\"" +groups = ["main", "dev"] files = [ - {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, - {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, + {file = "setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6"}, + {file = "setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a"}, ] +markers = {main = "extra == \"s2repoze\""} [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.13.0) ; sys_platform != \"cygwin\""] core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.18.*)", "pytest-mypy"] [[package]] name = "six" @@ -2073,4 +2073,4 @@ s2repoze = ["paste", "repoze.who", "zope.interface"] [metadata] lock-version = "2.1" python-versions = ">= 3.9" -content-hash = "04b0c0e0efb4781e75bd015e82e23592fb454f9bdeb42507a18c9e5a18d30029" +content-hash = "5861a1db5bbd7f1f578f5527c01566dc9bf549911e5c21ceffcbe501d976c433" diff --git a/pyproject.toml b/pyproject.toml index 87068b18d..72d964640 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ types-python-dateutil = "^2.8.19.6" types-setuptools = "^67.2.0.1" types-six = "^1.16.21.4" types-requests = "^2.28.11.12" +setuptools = "81.0.0" [tool.poetry.group.test] optional = true From cab005a9d0b79d16bc5b2d678da796149ae6a586 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 22:47:20 -0500 Subject: [PATCH 07/24] Add release jobs. --- .github/workflows/release-github.yml | 41 ++++++++++++++++ .github/workflows/release-pypi.yml | 72 ++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 .github/workflows/release-github.yml create mode 100644 .github/workflows/release-pypi.yml diff --git a/.github/workflows/release-github.yml b/.github/workflows/release-github.yml new file mode 100644 index 000000000..33783beee --- /dev/null +++ b/.github/workflows/release-github.yml @@ -0,0 +1,41 @@ +name: Attach artifacts to GitHub release + +on: + release: + types: [published] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + attach: + name: Build, attest, and attach to release + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write # upload assets to the GitHub release + id-token: write # required by attest-build-provenance + attestations: write # write to GitHub Attestations API + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install Poetry + run: pipx install poetry==2.4.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.x' + cache: poetry + - name: Build sdist and wheel + run: poetry build + - name: Generate build provenance attestation + uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 + with: + subject-path: 'dist/*' + - name: Upload distributions to GitHub release + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.event.release.tag_name }} + run: gh release upload "$TAG" dist/* diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml new file mode 100644 index 000000000..0c46ed1ff --- /dev/null +++ b/.github/workflows/release-pypi.yml @@ -0,0 +1,72 @@ +name: Publish to PyPI + +on: + release: + types: [published] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Install Poetry + run: pipx install poetry==2.4.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.x' + cache: poetry + - name: Build sdist and wheel + run: poetry build + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: python-package-distributions + path: dist/ + + publish-to-testpypi: + name: Publish to TestPyPI + if: github.event.release.prerelease == true + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: + name: testpypi + url: https://test.pypi.org/p/pysaml2 + permissions: + id-token: write # Trusted Publishing OIDC + auto-generated PEP 740 attestations + attestations: write # GitHub Attestations API + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: python-package-distributions + path: dist/ + - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 + with: + repository-url: https://test.pypi.org/legacy/ + + publish-to-pypi: + name: Publish to PyPI + if: github.event.release.prerelease == false + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + environment: + name: pypi + url: https://pypi.org/p/pysaml2 + permissions: + id-token: write + attestations: write + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: python-package-distributions + path: dist/ + - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 From 13178ca4158c5a633e92adf13bf8562c28188f8a Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 22:52:36 -0500 Subject: [PATCH 08/24] Rename file. --- .github/workflows/{lint.yml => lints.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{lint.yml => lints.yml} (98%) diff --git a/.github/workflows/lint.yml b/.github/workflows/lints.yml similarity index 98% rename from .github/workflows/lint.yml rename to .github/workflows/lints.yml index 111a92929..9e499d459 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lints.yml @@ -1,4 +1,4 @@ -name: lint +name: lints on: push: From 90ddcd7d5fc1a1503df2d5865c7764b8c3a9eb65 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 23:21:39 -0500 Subject: [PATCH 09/24] Publish to `jmsmtn-pysaml2` --- .github/workflows/release-pypi.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index 0c46ed1ff..919adac47 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -39,7 +39,7 @@ jobs: timeout-minutes: 10 environment: name: testpypi - url: https://test.pypi.org/p/pysaml2 + url: https://test.pypi.org/p/jmsmtn-pysaml2 permissions: id-token: write # Trusted Publishing OIDC + auto-generated PEP 740 attestations attestations: write # GitHub Attestations API @@ -60,7 +60,7 @@ jobs: timeout-minutes: 10 environment: name: pypi - url: https://pypi.org/p/pysaml2 + url: https://pypi.org/p/jmsmtn-pysaml2 permissions: id-token: write attestations: write From bf08278b04757d141ceb28ef556a2d968cd50821 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 23:41:41 -0500 Subject: [PATCH 10/24] Change the name of the project as well for testing. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 72d964640..c338e96a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "pysaml2" +name = "jmsmtn-pysaml2" version = "7.5.4" description = "Python implementation of SAML Version 2 Standard" license = "Apache-2.0" From aa357793c4e8cb0f91746d40062369d8db1ea62e Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 23:44:21 -0500 Subject: [PATCH 11/24] Change version. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c338e96a3..d428c71cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "jmsmtn-pysaml2" -version = "7.5.4" +version = "7.5.5rc2" description = "Python implementation of SAML Version 2 Standard" license = "Apache-2.0" authors = [{name = "IdentityPython", email = "discuss@idpy.org"}] From 6bd13fc97f5269e03709bd2fe245c6438d812897 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Fri, 8 May 2026 23:51:11 -0500 Subject: [PATCH 12/24] Change version. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d428c71cd..e01f63131 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "jmsmtn-pysaml2" -version = "7.5.5rc2" +version = "7.5.6" description = "Python implementation of SAML Version 2 Standard" license = "Apache-2.0" authors = [{name = "IdentityPython", email = "discuss@idpy.org"}] From 912c7c919e5da3fff4d6396f638aa684ab70cfc7 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 00:01:37 -0500 Subject: [PATCH 13/24] Fix tests --- src/saml2/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/saml2/version.py b/src/saml2/version.py index 8ef1c21e5..0ff3add9f 100644 --- a/src/saml2/version.py +++ b/src/saml2/version.py @@ -2,7 +2,7 @@ def _parse_version(): - value = _resolve_package_version("pysaml2") + value = _resolve_package_version("jmsmtn-pysaml2") return value From 63c83c20ca32066f7cec6cd2d7a61b9584085f0d Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 00:16:59 -0500 Subject: [PATCH 14/24] Revert testing commits. --- .github/workflows/release-pypi.yml | 4 ++-- pyproject.toml | 4 ++-- src/saml2/version.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index 919adac47..0c46ed1ff 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -39,7 +39,7 @@ jobs: timeout-minutes: 10 environment: name: testpypi - url: https://test.pypi.org/p/jmsmtn-pysaml2 + url: https://test.pypi.org/p/pysaml2 permissions: id-token: write # Trusted Publishing OIDC + auto-generated PEP 740 attestations attestations: write # GitHub Attestations API @@ -60,7 +60,7 @@ jobs: timeout-minutes: 10 environment: name: pypi - url: https://pypi.org/p/jmsmtn-pysaml2 + url: https://pypi.org/p/pysaml2 permissions: id-token: write attestations: write diff --git a/pyproject.toml b/pyproject.toml index e01f63131..72d964640 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "jmsmtn-pysaml2" -version = "7.5.6" +name = "pysaml2" +version = "7.5.4" description = "Python implementation of SAML Version 2 Standard" license = "Apache-2.0" authors = [{name = "IdentityPython", email = "discuss@idpy.org"}] diff --git a/src/saml2/version.py b/src/saml2/version.py index 0ff3add9f..8ef1c21e5 100644 --- a/src/saml2/version.py +++ b/src/saml2/version.py @@ -2,7 +2,7 @@ def _parse_version(): - value = _resolve_package_version("jmsmtn-pysaml2") + value = _resolve_package_version("pysaml2") return value From 13e33487d7fde28158fccc708d1c87431e4a3b2d Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 10:48:06 -0500 Subject: [PATCH 15/24] markdown lints --- RELEASE.md | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index d2dd6b93b..358cb2e11 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,22 +1,22 @@ -## Release instructions +# Release instructions When releasing a new version, the following steps should be taken: 1. Make sure the package metadata in `pyproject.toml` is up-to-date. - ``` + ```shell poetry check ``` 2. Make sure all automated tests pass: - ``` + ```shell poetry run pytest ``` 3. Bump the version of the package - ``` + ```shell poetry version -- X.Y.Z ``` @@ -24,34 +24,33 @@ When releasing a new version, the following steps should be taken: 5. Commit and sign the changes: - ``` + ```shell git add -u # CHANGELOG.md pyproject.toml git commit -v -s -m "Release version X.Y.Z" ``` 6. Create a signed release [tag]: - ``` + ```shell git tag -a -s vX.Y.Z -m "Version X.Y.Z" ``` 7. Push the changes and the release to Github: - ``` + ```shell git push --follow-tags ``` 8. Publish the release on PyPI: - ``` + ```shell poetry publish --build ``` 9. Send an email to the pysaml2 list announcing this release - - [VERSION]: https://github.com/IdentityPython/pysaml2/blob/master/VERSION - [CHANGELOG.md]: https://github.com/IdentityPython/pysaml2/blob/master/CHANGELOG.md - [docutils]: http://docutils.sourceforge.net/ - [branch]: https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell - [tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging#_annotated_tags +[VERSION]: https://github.com/IdentityPython/pysaml2/blob/master/VERSION +[CHANGELOG.md]: https://github.com/IdentityPython/pysaml2/blob/master/CHANGELOG.md +[docutils]: http://docutils.sourceforge.net/ +[branch]: https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell +[tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging#_annotated_tags From 27fd34f015915b39d8a644de97574a30d1f4cda0 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 10:54:08 -0500 Subject: [PATCH 16/24] Add new release instructions. --- RELEASE.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 358cb2e11..eced319e8 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -41,12 +41,26 @@ When releasing a new version, the following steps should be taken: git push --follow-tags ``` -8. Publish the release on PyPI: +8. Publish the release. Creating the GitHub release fires the + `Publish to PyPI` and `Attach artifacts to GitHub release` workflows, + which build the distributions, generate PEP 740 attestations, and + upload to PyPI/TestPyPI via Trusted Publishing. + + Pre-release path (publishes to TestPyPI): + + ```shell + gh release create v7.5.5rc1 --prerelease --title "v7.5.5rc1" --notes "Pre-release for CI" --target + ``` + + Full release path (publishes to PyPI): ```shell - poetry publish --build + gh release create v7.5.5 --title "v7.5.5" --notes "Release" --target master ``` + Or via the UI: Releases -> Draft a new release -> choose/create tag -> + tick (or untick) "Set as a pre-release" -> Publish release. + 9. Send an email to the pysaml2 list announcing this release [VERSION]: https://github.com/IdentityPython/pysaml2/blob/master/VERSION From ac7903d4afcfd27e6e1bed5729bbc005a22fda54 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 10:55:11 -0500 Subject: [PATCH 17/24] Remove unused links. --- RELEASE.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index eced319e8..c8495a25e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -63,8 +63,5 @@ When releasing a new version, the following steps should be taken: 9. Send an email to the pysaml2 list announcing this release -[VERSION]: https://github.com/IdentityPython/pysaml2/blob/master/VERSION [CHANGELOG.md]: https://github.com/IdentityPython/pysaml2/blob/master/CHANGELOG.md -[docutils]: http://docutils.sourceforge.net/ -[branch]: https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell [tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging#_annotated_tags From 3773e0576a61e166070351138a3f8ef6395a286d Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 10:59:30 -0500 Subject: [PATCH 18/24] Remove travis. --- .travis.yml | 102 ---------------------------------------------------- 1 file changed, 102 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b9aae06fc..000000000 --- a/.travis.yml +++ /dev/null @@ -1,102 +0,0 @@ -os: linux -dist: bionic -language: python - -services: - - mongodb - -before_install: - - sudo apt-get install -y xmlsec1 - -install: - - pip install tox - - pip install tox-travis - -script: - - tox - -jobs: - allow_failures: - - python: 3.10-dev - - python: pypy3 - include: - - python: 3.6 - - python: 3.7 - - python: 3.8 - - python: 3.9 - - python: 3.10-dev - - python: pypy3 - - - stage: Expose env-var information - script: | - cat <VERSION - deploy: - - provider: pypi - distributions: sdist bdist_wheel - server: "https://test.pypi.org/legacy/" - skip_cleanup: true - username: "__token__" - password: "$PYPI_PRE_RELEASE_TOKEN" - on: - repo: IdentityPython/pysaml2 - - - stage: Deploy new release on PyPI - if: type = push AND tag IS present - before_install: skip - install: skip - script: skip - deploy: - - provider: pypi - distributions: sdist bdist_wheel - username: "__token__" - password: "$PYPI_RELEASE_TOKEN" - on: - repo: IdentityPython/pysaml2 - tags: true - - - stage: Deploy new release on GitHub - if: type = push AND tag IS present - before_install: skip - install: skip - script: skip - deploy: - - provider: releases - token: "$GITHUB_RELEASE_TOKEN" - on: - repo: IdentityPython/pysaml2 - tags: true From 366786e91b158262c5c16a27d1896df1d820bb2c Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 11:00:31 -0500 Subject: [PATCH 19/24] Add a note about `gh` cli. --- RELEASE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASE.md b/RELEASE.md index c8495a25e..e82a69cf4 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -46,6 +46,8 @@ When releasing a new version, the following steps should be taken: which build the distributions, generate PEP 740 attestations, and upload to PyPI/TestPyPI via Trusted Publishing. + Install the [GitHub CLI] if you don't already have it (`brew install gh` on macOS). + Pre-release path (publishes to TestPyPI): ```shell @@ -64,4 +66,5 @@ When releasing a new version, the following steps should be taken: 9. Send an email to the pysaml2 list announcing this release [CHANGELOG.md]: https://github.com/IdentityPython/pysaml2/blob/master/CHANGELOG.md +[GitHub CLI]: https://cli.github.com/ [tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging#_annotated_tags From b7f9c3efac42d691d912e89806aee3f23683f0ff Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 11:01:07 -0500 Subject: [PATCH 20/24] update CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ef7fed65..284e5ed11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased (2026-XX-XX) + +- ci: Migrate from Travis CI to GitHub Actions +- ci: Add `tests` workflow with a Python 3.9–3.14 matrix +- ci: Add `lint` workflow (black, isort, flake8) and `checks` workflow (`poetry check` + lockfile validation) +- ci: Publish to PyPI/TestPyPI via Trusted Publishing with attestations +- ci: Attach build artifacts and provenance attestations to GitHub releases +- docs: Update RELEASE.md with the `gh release create` flow ## v7.5.4 (2025-10-07) From 5469e40ef9855e613ff3361ab7aaf0a3c9fb27a6 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 12:06:15 -0500 Subject: [PATCH 21/24] Apply zizmor static analysis recommendations. https://github.com/zizmorcore/zizmor --- .github/workflows/checks.yml | 2 ++ .github/workflows/lints.yml | 2 ++ .github/workflows/release-github.yml | 2 ++ .github/workflows/release-pypi.yml | 2 ++ .github/workflows/tests.yml | 2 ++ 5 files changed, 10 insertions(+) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 31568dcd8..28f228da4 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -18,6 +18,8 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Install Poetry run: pipx install poetry==2.4.0 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml index 9e499d459..b9918a92e 100644 --- a/.github/workflows/lints.yml +++ b/.github/workflows/lints.yml @@ -18,6 +18,8 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Install Poetry run: pipx install poetry==2.4.0 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/release-github.yml b/.github/workflows/release-github.yml index 33783beee..727e7ebb8 100644 --- a/.github/workflows/release-github.yml +++ b/.github/workflows/release-github.yml @@ -22,6 +22,8 @@ jobs: attestations: write # write to GitHub Attestations API steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Install Poetry run: pipx install poetry==2.4.0 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index 0c46ed1ff..b5fb387a2 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -18,6 +18,8 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Install Poetry run: pipx install poetry==2.4.0 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2e3a68d02..d25ab214f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,8 @@ jobs: python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Install xmlsec1 run: | sudo apt-get update From 5328246fa570677fe484c722ae614f0247615c0d Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 12:07:39 -0500 Subject: [PATCH 22/24] Add a zizmor step. https://github.com/zizmorcore/zizmor --- .github/workflows/zizmor.yml | 29 +++++++++++++++++++++++++++++ CHANGELOG.md | 1 + 2 files changed, 30 insertions(+) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 000000000..86792febd --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,29 @@ +name: zizmor + +on: + push: + branches: [master] + pull_request: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + zizmor: + name: Audit GitHub Actions workflows + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Install zizmor + run: pipx install zizmor==1.24.1 + - name: Run zizmor + run: zizmor ./.github + env: + GH_TOKEN: ${{ github.token }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 284e5ed11..432faa13d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - ci: Add `lint` workflow (black, isort, flake8) and `checks` workflow (`poetry check` + lockfile validation) - ci: Publish to PyPI/TestPyPI via Trusted Publishing with attestations - ci: Attach build artifacts and provenance attestations to GitHub releases +- ci: Add `zizmor` workflow to audit GitHub Actions for security issues - docs: Update RELEASE.md with the `gh release create` flow ## v7.5.4 (2025-10-07) From 7901b1f340aeed39631e170093e1698484c143a5 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 12:10:43 -0500 Subject: [PATCH 23/24] Test PyPi releases again. Revert "Revert testing commits." This reverts commit 63c83c20ca32066f7cec6cd2d7a61b9584085f0d. --- .github/workflows/release-pypi.yml | 4 ++-- pyproject.toml | 4 ++-- src/saml2/version.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index b5fb387a2..f90382e6a 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -41,7 +41,7 @@ jobs: timeout-minutes: 10 environment: name: testpypi - url: https://test.pypi.org/p/pysaml2 + url: https://test.pypi.org/p/jmsmtn-pysaml2 permissions: id-token: write # Trusted Publishing OIDC + auto-generated PEP 740 attestations attestations: write # GitHub Attestations API @@ -62,7 +62,7 @@ jobs: timeout-minutes: 10 environment: name: pypi - url: https://pypi.org/p/pysaml2 + url: https://pypi.org/p/jmsmtn-pysaml2 permissions: id-token: write attestations: write diff --git a/pyproject.toml b/pyproject.toml index 72d964640..e01f63131 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "pysaml2" -version = "7.5.4" +name = "jmsmtn-pysaml2" +version = "7.5.6" description = "Python implementation of SAML Version 2 Standard" license = "Apache-2.0" authors = [{name = "IdentityPython", email = "discuss@idpy.org"}] diff --git a/src/saml2/version.py b/src/saml2/version.py index 8ef1c21e5..0ff3add9f 100644 --- a/src/saml2/version.py +++ b/src/saml2/version.py @@ -2,7 +2,7 @@ def _parse_version(): - value = _resolve_package_version("pysaml2") + value = _resolve_package_version("jmsmtn-pysaml2") return value From cd34a35707df9ea6a5d3333999ffbc10b5325d30 Mon Sep 17 00:00:00 2001 From: jmsmtn <19962890+jmsmtn@users.noreply.github.com> Date: Sat, 9 May 2026 12:17:28 -0500 Subject: [PATCH 24/24] Revert "Test PyPi releases again." This reverts commit 7901b1f340aeed39631e170093e1698484c143a5. --- .github/workflows/release-pypi.yml | 4 ++-- pyproject.toml | 4 ++-- src/saml2/version.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index f90382e6a..b5fb387a2 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -41,7 +41,7 @@ jobs: timeout-minutes: 10 environment: name: testpypi - url: https://test.pypi.org/p/jmsmtn-pysaml2 + url: https://test.pypi.org/p/pysaml2 permissions: id-token: write # Trusted Publishing OIDC + auto-generated PEP 740 attestations attestations: write # GitHub Attestations API @@ -62,7 +62,7 @@ jobs: timeout-minutes: 10 environment: name: pypi - url: https://pypi.org/p/jmsmtn-pysaml2 + url: https://pypi.org/p/pysaml2 permissions: id-token: write attestations: write diff --git a/pyproject.toml b/pyproject.toml index e01f63131..72d964640 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] -name = "jmsmtn-pysaml2" -version = "7.5.6" +name = "pysaml2" +version = "7.5.4" description = "Python implementation of SAML Version 2 Standard" license = "Apache-2.0" authors = [{name = "IdentityPython", email = "discuss@idpy.org"}] diff --git a/src/saml2/version.py b/src/saml2/version.py index 0ff3add9f..8ef1c21e5 100644 --- a/src/saml2/version.py +++ b/src/saml2/version.py @@ -2,7 +2,7 @@ def _parse_version(): - value = _resolve_package_version("jmsmtn-pysaml2") + value = _resolve_package_version("pysaml2") return value