Skip to content

Commit

Permalink
Merge pull request #109 from italia/dev
Browse files Browse the repository at this point in the history
v0.4.6

* fix: dependency installation
* Code Coverage
* feat: added python 3.10 in CI
* chore: entity statement and trust mark content type, onboarding tools html …
  • Loading branch information
peppelinux committed Mar 10, 2022
2 parents d25bdee + 3281000 commit b577e36
Show file tree
Hide file tree
Showing 24 changed files with 239 additions and 63 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
python-version:
- '3.8'
- '3.9'
- '3.10'

steps:
- uses: actions/checkout@v2
Expand All @@ -46,7 +47,7 @@ jobs:
flake8 ./spid_cie_oidc --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 ./spid_cie_oidc --count --exit-zero --statistics --max-line-length 120
- name: spid_cie_oidc onboarding and entity test
- name: spid_cie_oidc unit tests
run: |
cp examples/federation_authority/federation_authority/settingslocal.py.example examples/federation_authority/federation_authority/settingslocal.py
coverage erase
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ each of these can be installed separately within a django project. These are the
| __spid_cie_oidc.accounts__ | Customizable application that extends the django User model. |
| __spid_cie_oidc.entity__ | Openid Connect Federation django app that implements OIDC Federation 1.0 Entity Statements, metadata discovery, Trust Chain, Trust Marks and Metadata policy. Technical specifications: [__OIDC Federation Entity__](docs/technical_specifications/ENTITY.md) |
| __spid_cie_oidc.authority__ | Openid Connect Federation API and models for __OIDC Federation Authority/Intermediary__, [Technical specifications](docs/technical_specifications/AUTHORITY.md) and [tutorial](docs/CREATE_A_FEDERATION_AUTHORITY.md).
| __spid_cie_oidc.onboarding__ | [__Openid Connect Federation onboarding demo service__](docs/technical_specifications/ONBOARDING.md) |
| __spid_cie_oidc.onboarding__ | [__Openid Connect Federation onboarding demo service__](docs/technical_specifications/ONBOARDING.md) and tools|
| __spid_cie_oidc.relying_party__ | [__Openid Connect Relying Party__](docs/technical_specifications/RELYING_PARTY.md) and test suite for OIDC Providers |
| __spid_cie_oidc.provider__ | [__Openid Connect Provider__](docs/technical_specifications/PROVIDER.md) and test suite for OIDC Relying Parties |

Expand Down Expand Up @@ -80,6 +80,9 @@ Examples User, Password:

- admin oidcadmin

![OIDC Tools](docs/images/onboard_tools_jwt_debug.png)
OIDC tools that facilitate the lives of developers and service operators, here a simple interface to decode and verify a JWT.

## Contribute

Your contribution is welcome, no question is useless and no answer is obvious, we need you.
Expand Down
Binary file added docs/images/onboard_tools_jwt_debug.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"pydantic[email]"
]

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

with open(f'{SRC_FOLDER}{os.path.sep}{PKG_NAME}/__init__.py', 'r') as fd:
Expand Down
2 changes: 1 addition & 1 deletion spid_cie_oidc/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.4.5"
__version__ = "0.4.6"
7 changes: 6 additions & 1 deletion spid_cie_oidc/authority/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def entity_statement_as_jws(self, iss: str = None, aud: list = None) -> str:
self.entity_statement_as_dict(iss, aud),
issuer.jwks[0],
alg=issuer.default_signature_alg,
typ="entity-statement+jwt"
)

def __str__(self):
Expand Down Expand Up @@ -271,11 +272,15 @@ def trust_mark_as_jws(self):
self.trust_mark_as_dict,
self.issuer.jwks[0],
alg=self.issuer.default_signature_alg,
typ="trust-mark+jwt"
)

@property
def trust_mark(self):
return {"id": self.profile.profile_id, "trust_mark": self.trust_mark_as_jws}
return {
"id": self.profile.profile_id,
"trust_mark": self.trust_mark_as_jws
}

def __str__(self):
return f"{self.profile} [{self.descendant}]"
Expand Down
8 changes: 4 additions & 4 deletions spid_cie_oidc/authority/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def fetch(request):
return JsonResponse(conf.entity_configuration_as_dict, safe=False)
else:
return HttpResponse(
conf.entity_configuration_as_jws, content_type="application/jose"
conf.entity_configuration_as_jws, content_type="application/entity-statement+jwt"
)

sub = FederationDescendant.objects.filter(
Expand All @@ -45,7 +45,7 @@ def fetch(request):
else:
return HttpResponse(
sub.entity_statement_as_jws(iss.sub, request.GET.get("aud")),
content_type="application/jose",
content_type="application/entity-statement+jwt",
)


Expand Down Expand Up @@ -85,7 +85,7 @@ def resolve_entity_statement(request, format: str = "jose"):
iss = get_first_self_trust_anchor()

_q = dict(
sub=request.GET["sub"],
sub=request.GET["sub"],
trust_anchor__sub=request.GET["anchor"],
is_active=True
)
Expand All @@ -104,7 +104,7 @@ def resolve_entity_statement(request, format: str = "jose"):
raise Http404("entity metadata type not found.")
else:
entity = typed_entity

try:
tc_data = dict(
httpc_params=HTTPC_PARAMS,
Expand Down
1 change: 1 addition & 0 deletions spid_cie_oidc/entity/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ def entity_configuration_as_jws(self, **kwargs):
self.entity_configuration_as_dict,
self.jwks[0],
alg=self.default_signature_alg,
typ="entity-statement+jwt",
**kwargs,
)

Expand Down
2 changes: 0 additions & 2 deletions spid_cie_oidc/entity/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,9 @@ def combine_claim_policy(superior, child):
:param superior: Superior policy
:param child: Intermediates policy
"""

# weed out everything I don't recognize
superior_set = set(superior).intersection(POLICY_FUNCTIONS)
child_set = set(child).intersection(POLICY_FUNCTIONS)

if "value" in superior_set: # An exact value can not be restricted.
if child_set:
if "essential" in child_set:
Expand Down
70 changes: 69 additions & 1 deletion spid_cie_oidc/entity/tests/test_05_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

from django.test import TestCase
from spid_cie_oidc.authority.settings import FEDERATION_DEFAULT_POLICY
from spid_cie_oidc.entity.policy import apply_policy, gather_policies
from spid_cie_oidc.entity.policy import (
PolicyError,
apply_policy,
combine_claim_policy, diff2policy,
gather_policies
)
from spid_cie_oidc.entity.tests.rp_metadata_settings import RP_METADATA


Expand All @@ -24,3 +29,66 @@ def test_apply_policy(self):
combined_contacts = combined_policy["contacts"]
self.assertTrue("[email protected]" in combined_contacts)
self.assertTrue("[email protected]" in combined_contacts)

def test_diff_two_policy(self):
fa_policy_old = {}
fa_policy_old["scopes"] = FEDERATION_DEFAULT_POLICY["openid_relying_party"][
"scopes"
]
fa_policy_new = deepcopy(fa_policy_old)
fa_policy_new["contacts"] = {"add": "[email protected]"}
result = diff2policy(fa_policy_new, fa_policy_old)
diff = {
"contacts": {
"add": {
"add": "[email protected]"
}
}
}
self.assertTrue(result == diff)

def test_combine_claim_policy(self):
superior = {}
child = {}
combined = combine_claim_policy(superior, child)
self.assertEquals(combined, {})
superior = {"value" : "val"}
child = {"essential": False}
combined = combine_claim_policy(superior, child)
self.assertEquals(combined, {"value": "val", "essential": False})
child = {"essential": "ess", "add" : "added"}
with self.assertRaises(PolicyError):
combine_claim_policy(superior, child)
child = {"value": "valChild", "add" : "added"}
with self.assertRaises(PolicyError):
combine_claim_policy(superior, child)
child = {"value": "val", "add" : "added"}
combined = combine_claim_policy(superior, child)
self.assertEquals(combined, superior)
child = {"add" : "added"}
with self.assertRaises(PolicyError):
combine_claim_policy(superior, child)
child = {}
combined = combine_claim_policy(superior, child)
self.assertEquals(combined, superior)
superior = {"essential": True}
child = {"essential": False}
with self.assertRaises(PolicyError):
combine_claim_policy(superior, child)
child = {"essential": True, "one_of" : True, "subset_of": []}
with self.assertRaises(PolicyError):
combine_claim_policy(superior, child)
superior = { "superset_of": []}
child = { "subset_of": []}
combined = combine_claim_policy(superior, child)
self.assertEquals(combined, {'superset_of': [], 'subset_of': []})
child = { "subset_of": [], "default" : ""}
combined = combine_claim_policy(superior, child)
self.assertEquals(combined, {'subset_of': [], 'superset_of': [], 'default': ''})
child = { "default" : ""}
combined = combine_claim_policy(superior, child)
self.assertEquals(combined, {'superset_of': [], 'default': ''})
superior = { "subset_of": []}
combined = combine_claim_policy(superior, child)
self.assertEquals(combined, {'subset_of': [], 'default': ''})

29 changes: 29 additions & 0 deletions spid_cie_oidc/entity/tests/test_07_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.test import TestCase
from spid_cie_oidc.authority.tests.settings import (
RP_METADATA_JWK1,
RP_METADATA_JWK1_pub
)
from spid_cie_oidc.entity.validators import (
validate_private_jwks,
validate_public_jwks
)


class ValidatrTest(TestCase):

def setUp(self):
pass

def test_validate_public_jwks_with_private_key(self):
with self.assertRaises(Exception):
validate_public_jwks(RP_METADATA_JWK1)

def test_validate_private_jwks_with_public_key(self):
with self.assertRaises(Exception):
validate_private_jwks(RP_METADATA_JWK1_pub)

def test_validate_public_jwks(self):
validate_public_jwks(RP_METADATA_JWK1_pub)

def test_validate_private_jwks(self):
validate_private_jwks(RP_METADATA_JWK1)
2 changes: 1 addition & 1 deletion spid_cie_oidc/entity/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ def entity_configuration(request):
return JsonResponse(conf.entity_configuration_as_dict, safe=False)
else:
return HttpResponse(
conf.entity_configuration_as_jws, content_type="application/jose"
conf.entity_configuration_as_jws, content_type="application/entity-statement+jwt"
)
16 changes: 8 additions & 8 deletions spid_cie_oidc/onboarding/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -84,32 +84,32 @@
<h3 class="no_toc">JWK</h3>
</li>
<li><a class="list-item" href="{% url 'oidc_onboarding_create_jwk'%}">
<span>Create a jwk</span></a></li>
<span>{% trans "Create a jwk" %}</span></a></li>
<li><a class="list-item" href="{% url 'oidc_onboarding_convert_jwk'%}?type=private">
<span>Convert a private jwk to PEM certificate/key</span></a></li>
<span>{% trans "Convert a private JWK to PEM" %}</span></a></li>
<li><a class="list-item" href="{% url 'oidc_onboarding_convert_jwk'%}?type=public">
<span>Convert a public jwk to PEM certificate/key</span></a></li>
<span>{% trans "Convert a public JWK to PEM" %}</span></a></li>
<li><a class="list-item" href="{% url 'oidc_onboarding_tools_decode_jwt' %}">
<span>Decode a jwt and verify if a jwk is also submitted</span></a></li>
<span>{% trans "JWT decode and verification" %}</span></a></li>
</ul>
</div>
</div>
<div class="col-12 col-lg-4">
<div class="link-list-wrapper">
<ul class="link-list">
<li>
<h3 class="no_toc">Trust Chain/ Trust mark</h3>
<h3 class="no_toc">Trust Chain / Trust mark</h3>
</li>
<li><a class="list-item" href="{% url 'oidc_onboarding_resolve_statement' %}"><span>Resolve entity statement</span></a></li>
<li><a class="list-item" href="{% url 'oidc_onboarding_tools_validating_trustmark' %}"><span>Validating a trust mark</span></a></li>
<li><a class="list-item" href="{% url 'oidc_onboarding_resolve_statement' %}"><span>{% trans "Resolve entity statement" %}</span></a></li>
<li><a class="list-item" href="{% url 'oidc_onboarding_tools_validating_trustmark' %}"><span>{% trans "Trust mark validation" %}</span></a></li>
</ul>
</div>
</div>
<div class="col-12 col-lg-4">
<div class="link-list-wrapper">
<ul class="link-list">
<li>
<h3 class="no_toc">Schemas</h3>
<h3 class="no_toc">{% trans "Schemas" %}</h3>
</li>
<li><a class="list-item" href="{% url 'oidc_onboarding_schemas_Authorization' %}">
<span>Authorization Endpoint</span></a></li>
Expand Down
4 changes: 2 additions & 2 deletions spid_cie_oidc/onboarding/templates/onboarding_decode_jwt.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
<div class="col-12 pl-lg-4">

<h4 class="text-left">
{% trans "Decode or verify jwt" %}
{% trans "JWT decode and verification" %}
</h4>

<p class="card-title">
{% trans "Enter your signed jwt and jwk to get jwt decryption and verification" %}
{% trans "Submit a jwt to decode it, if you submit also a jwk you'll decrypt or verify it." %}
</p>
<form method="post" action="">
{% csrf_token %}
Expand Down
19 changes: 13 additions & 6 deletions spid_cie_oidc/onboarding/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

from spid_cie_oidc.onboarding.schemas.jwt import JwtStructure


def onboarding_landing(request):
return render(request, "onboarding_landing.html")

Expand Down Expand Up @@ -177,6 +178,7 @@ def onboarding_decode_jwt(request):
render(request, 'onboarding_decode_jwt.html', context)
return render(request, 'onboarding_decode_jwt.html', context)


def onboarding_schemas_authorization(request):
auth_request = AuthenticationRequestSpid.schema_json(indent=2)
auth_res_succ = AuthenticationResponse.schema_json(indent=2)
Expand All @@ -186,7 +188,8 @@ def onboarding_schemas_authorization(request):
auth_res_succ = auth_res_succ,
auth_res_err = auth_res_err
)
return render (request, "onboarding_schemas_authorization.html", content)
return render(request, "onboarding_schemas_authorization.html", content)


def onboarding_schemas_introspection(request):
intro_request = IntrospectionRequest.schema_json(indent=2)
Expand All @@ -197,7 +200,8 @@ def onboarding_schemas_introspection(request):
intro_res_succ = intro_res_succ,
intro_res_err = intro_res_err
)
return render (request, "onboarding_schemas_introspection.html", content)
return render(request, "onboarding_schemas_introspection.html", content)


def onboarding_schemas_metadata(request):
op_meta_spid = OPMetadataSpid.schema_json(indent=2)
Expand All @@ -210,14 +214,16 @@ def onboarding_schemas_metadata(request):
rp_meta_spid = rp_meta_spid,
rp_meta_cie = rp_meta_cie
)
return render (request, "onboarding_schemas_metadata.html", content)
return render(request, "onboarding_schemas_metadata.html", content)


def onboarding_schemas_revocation(request):
revoc_request = RevocationRequest.schema_json(indent=2)
content = dict(
revoc_request = revoc_request,
)
return render (request, "onboarding_schemas_revocation.html", content)
return render(request, "onboarding_schemas_revocation.html", content)


def onboarding_schemas_token(request):
auth_code_req = TokenAuthnCodeRequest.schema_json(indent=2)
Expand All @@ -232,11 +238,12 @@ def onboarding_schemas_token(request):
refr_res = refr_res,
err_res = err_res
)
return render (request, "onboarding_schemas_token.html", content)
return render(request, "onboarding_schemas_token.html", content)


def onboarding_schemas_jwt_client_assertion(request):
jwt_client_asser = JwtStructure.schema_json(indent=2)
content = dict(
jwt_client_asser = jwt_client_asser,
)
return render (request, "onboarding_schemas_jwt_client_assertion.html", content)
return render(request, "onboarding_schemas_jwt_client_assertion.html", content)
1 change: 1 addition & 0 deletions spid_cie_oidc/provider/tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"token_endpoint": "http://op-test/oidc/op/token/",
"userinfo_endpoint": "http://op-test/oidc/op/userinfo/",
"introspection_endpoint": "http://op-test/oidc/op/introspection/",
"revocation_endpoint": "http://op-test/oidc/op/revocation/",
"claims_parameter_supported": True,
"contacts": ["[email protected]"],
"client_registration_types_supported": ["automatic"],
Expand Down
4 changes: 2 additions & 2 deletions spid_cie_oidc/provider/tests/test_02_authn_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,8 @@ def test_auth_request_no_correct_payload(self):
client = Client()
url = reverse("oidc_provider_authnrequest")
res = client.get(url, {"request": jws})
self.assertTrue(res.status_code == 302)
self.assertIn("error=invalid_request", res.url)
self.assertTrue(res.status_code == 200)
self.assertIn("error", res.content.decode())

@override_settings(OIDCFED_DEFAULT_TRUST_ANCHOR=TA_SUB)
def test_auth_request_invalid_session(self):
Expand Down
1 change: 0 additions & 1 deletion spid_cie_oidc/provider/tests/test_03_refresh_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ def setUp(self):
"client_id": RP_CLIENT_ID,
"scope": "openid",
}
User.objects.create()
session = OidcSession.objects.create(
user=User.objects.create(username = "username"),
user_uid="",
Expand Down
Loading

0 comments on commit b577e36

Please sign in to comment.