auth_oidc/management: add remote jwksets refresh command (#83841)

This commit is contained in:
Paul Marillonnet 2023-11-22 11:34:17 +01:00
parent 51820dcdbd
commit 57dd6b1a08
4 changed files with 107 additions and 0 deletions

View File

@ -23,6 +23,7 @@ cron2 = minute=0,hour=0,week=0 /usr/bin/authentic2-multitenant-manage tenant_com
cron2 = minute=0,hour=0,week=0 /usr/bin/authentic2-multitenant-manage tenant_command update-ldap-mapped-roles-list --all-tenants
# random sleep: try to avoid multiple machines overloading ldap server
cron2 = minute=10,unique=1,harakiri=14400 /bin/bash -c '/bin/sleep $[RANDOM %% 180]' && /usr/bin/authentic2-multitenant-manage tenant_command sync-ldap-users --all-tenants
cron2 = minute=20,unique=1,harakiri=14400 /bin/bash -c '/bin/sleep $[RANDOM %% 180]' && /usr/bin/authentic2-multitenant-manage tenant_command oidc-refresh-jwkset-json --all-tenants
cron2 = minute=30,hour=5,unique=1,harakiri=14400 /bin/bash -c '/bin/sleep $[RANDOM %% 180]' && /usr/bin/authentic2-multitenant-manage tenant_command deactivate-orphaned-ldap-users --all-tenants
cron2 = minute=0,hour=2,unique=1 /usr/bin/authentic2-multitenant-manage tenant_command roles-summary --all-tenants

View File

@ -21,6 +21,7 @@ cron2 = minute=5,unique=1 /usr/bin/authentic2-manage cleanupauthentic
cron2 = minute=0,hour=5,unique=1 /usr/bin/authentic2-manage clean-unused-accounts
cron2 = minute=0,hour=0,week=0,unique=1 /usr/bin/authentic2-manage clean-user-exports
cron2 = minute=10,unique=1,harakiri=14400 /usr/bin/authentic2-manage sync-ldap-users
cron2 = minute=20,unique=1,harakiri=14400 /usr/bin/authentic2-manage oidc-refresh-jwkset-json
cron2 = minute=30,hour=5,unique=1,harakiri=14400 /usr/bin/authentic2-manage deactivate-orphaned-ldap-users
master = true

View File

@ -0,0 +1,39 @@
# authentic2 - versatile identity manager
# Copyright (C) 2010-2023 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from authentic2.base_commands import LogToConsoleCommand
from authentic2_auth_oidc.models import OIDCProvider
class Command(LogToConsoleCommand):
loggername = 'authentic2_auth_oidc.models'
def core_command(self, *args, **kwargs):
logger = logging.getLogger(self.loggername)
providers = OIDCProvider.objects.exclude(jwkset_url='')
if not providers.count():
logger.error('no provider whose JWKSet needs refresh, exiting')
return
logger.info(
'got %s provider(s): %s',
providers.count(),
' '.join(providers.values_list('slug', flat=True)),
)
for provider in providers:
provider.set_jwkset_json_from_url()
provider.save()

View File

@ -24,6 +24,7 @@ from io import BufferedReader, BufferedWriter, TextIOWrapper
import httmock
import py
import pytest
import responses
import webtest
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
@ -895,3 +896,68 @@ def test_oidc_sync_provider(
assert User.objects.filter(first_name='Mod', last_name='Ified').count() in range(
20 - deletion_number, 21
)
@responses.activate
def test_auth_oidc_refresh_jwkset_json(db, app, admin, settings, caplog):
jwkset_url = 'https://www.example.com/common/discovery/v3.0/keys'
kid_rsa = '123'
kid_ec = '456'
def generate_remote_jwkset_json():
key_rsa = JWK.generate(kty='RSA', size=512, kid=kid_rsa)
key_ec = JWK.generate(kty='EC', size=256, kid=kid_ec)
jwkset = JWKSet()
jwkset.add(key_rsa)
jwkset.add(key_ec)
return jwkset.export(as_dict=True)
responses.get(
jwkset_url,
json={
'headers': {
'content-type': 'application/json',
},
'status_code': 200,
**generate_remote_jwkset_json(),
},
)
issuer = ('https://www.example.com',)
provider = OIDCProvider.objects.create(
ou=get_default_ou(),
name='Foo',
slug='foo',
client_id='abc',
client_secret='def',
enabled=True,
issuer=issuer,
authorization_endpoint='%s/authorize' % issuer,
token_endpoint='%s/token' % issuer,
end_session_endpoint='%s/logout' % issuer,
userinfo_endpoint='%s/user_info' % issuer,
token_revocation_endpoint='%s/revoke' % issuer,
jwkset_url=jwkset_url,
idtoken_algo=OIDCProvider.ALGO_RSA,
claims_parameter_supported=False,
button_label='Connect with Foo',
)
assert {key['kid'] for key in provider.jwkset_json['keys']} == {'123', '456'}
kid_rsa = 'abcdefg'
kid_ec = 'hijklmn'
responses.get(
jwkset_url,
json={
'headers': {
'content-type': 'application/json',
},
'status_code': 200,
**generate_remote_jwkset_json(),
},
)
call_command('oidc-refresh-jwkset-json', '-v1')
provider.refresh_from_db()
assert {key['kid'] for key in provider.jwkset_json['keys']} == {'abcdefg', 'hijklmn'}