ory hydra update to version 2

master
TuxCoder 2023-03-17 08:52:33 +01:00
parent 4a31250bca
commit 65ceb2abbd
6 changed files with 52 additions and 44 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix"
}

View File

@ -3,7 +3,7 @@ from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import rsa, dh
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.x509 import ObjectIdentifier
from pathlib import Path
@ -109,6 +109,9 @@ class Pki(object):
_public_key = serialization.load_pem_public_key(
publickey.encode(), backend=default_backend())
if isinstance(_public_key, dh.DHPublicKey):
raise AssertionError('key can not be a dsa key')
ca_private_key, ca_cert = self._init_ca(service)
ca_name = service.name
username = str(user.username)

View File

@ -6,8 +6,8 @@ from flask.typing import ResponseReturnValue
from flask_login import current_user, logout_user
from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
from authlib.integrations.base_client.errors import InvalidTokenError
from ory_hydra_client.api.admin import list_o_auth_2_clients, get_o_auth_2_client, update_o_auth_2_client, create_o_auth_2_client
from ory_hydra_client.models import OAuth2Client, GenericError
from ory_hydra_client.api.o_auth_2 import list_o_auth_2_clients, get_o_auth_2_client, set_o_auth_2_client, create_o_auth_2_client
from ory_hydra_client.models import OAuth20Client, GenericError
from typing import Optional
from collections.abc import Iterable
import logging
@ -77,7 +77,7 @@ def registration_accept(registration_id) -> ResponseReturnValue:
@admin_views.route('/clients')
async def clients() -> ResponseReturnValue:
clients = await list_o_auth_2_clients.asyncio(_client=hydra_service.hydra_client)
clients = await list_o_auth_2_clients.asyncio_detailed(_client=hydra_service.hydra_client)
return render_template('admin/clients.html.j2', clients=clients)
@admin_views.route('/client/<client_id>', methods=['GET', 'POST'])
@ -92,7 +92,7 @@ async def client(client_id: str) -> ResponseReturnValue:
if form.validate_on_submit():
form.populate_obj(client)
client = await update_o_auth_2_client.asyncio(id=client_id ,json_body=client, _client=hydra_service.hydra_client)
client = await set_o_auth_2_client.asyncio(id=client_id ,json_body=client, _client=hydra_service.hydra_client)
if client is None or isinstance(client, GenericError):
logger.error(f"oauth2 client update failed: '{client_id}'")
return 'client update failed', 500
@ -105,7 +105,7 @@ async def client(client_id: str) -> ResponseReturnValue:
@admin_views.route('/client_new', methods=['GET','POST'])
async def client_new() -> ResponseReturnValue:
client = OAuth2Client()
client = OAuth20Client()
form = OAuth2ClientForm()
if form.validate_on_submit():

View File

@ -16,7 +16,7 @@ import secrets
from ..model import db, User
from ..hydra import hydra_service
from ..lenticular_services import lenticular_services
from ory_hydra_client.api.admin import introspect_o_auth_2_token
from ory_hydra_client.api.o_auth_2 import introspect_o_auth_2_token
from ory_hydra_client.models import GenericError

View File

@ -17,8 +17,9 @@ import crypt
from datetime import datetime
import logging
import json
from ory_hydra_client.api.admin import get_consent_request, accept_consent_request, accept_login_request, get_login_request, accept_login_request, accept_logout_request, get_login_request
from ory_hydra_client.models import AcceptLoginRequest, AcceptConsentRequest, ConsentRequestSession, GenericError, ConsentRequestSessionAccessToken, ConsentRequestSessionIdToken
from ory_hydra_client.api.o_auth_2 import get_o_auth_2_consent_request, accept_o_auth_2_consent_request, accept_o_auth_2_login_request, get_o_auth_2_login_request, accept_o_auth_2_login_request, accept_o_auth_2_logout_request, get_o_auth_2_login_request
from ory_hydra_client import models as ory_hydra_m
from ory_hydra_client.models import TheRequestPayloadUsedToAcceptALoginOrConsentRequest, TheRequestPayloadUsedToAcceptAConsentRequest, GenericError
from typing import Optional
from ..model import db, User, SecurityUser
@ -43,19 +44,11 @@ async def consent() -> ResponseReturnValue:
remember_for = 60*60*24*30 # remember for 30 days
#try:
consent_request = await get_consent_request.asyncio(consent_challenge=request.args['consent_challenge'],_client=hydra_service.hydra_client)
consent_request = await get_o_auth_2_consent_request.asyncio(consent_challenge=request.args['consent_challenge'],_client=hydra_service.hydra_client)
if consent_request is None or isinstance( consent_request, GenericError):
if consent_request is None or isinstance( consent_request, ory_hydra_m.OAuth20RedirectBrowserTo):
return redirect(url_for('frontend.index'))
# except ory_hydra_client.exceptions.ApiValueError:
# logger.info('ory exception - could not fetch user data ApiValueError')
# return redirect(url_for('frontend.index'))
# except ory_hydra_client.exceptions.ApiException:
# logger.exception('ory exception - could not fetch user data')
# return redirect(url_for('frontend.index'))
requested_scope = consent_request.requested_scope
requested_audiences = consent_request.requested_access_token_audience
@ -63,7 +56,7 @@ async def consent() -> ResponseReturnValue:
user = User.query.get(consent_request.subject) # type: Optional[User]
if user is None:
return 'internal error', 500
token_data = {
access_token = {
'name': str(user.username),
'preferred_username': str(user.username),
'username': str(user.username),
@ -73,22 +66,20 @@ async def consent() -> ResponseReturnValue:
#'family_name': '-',
'groups': [group.name for group in user.groups]
}
id_token_data = {}
id_token = {}
if isinstance(requested_scope, list) and 'openid' in requested_scope:
id_token_data = token_data
access_token=ConsentRequestSessionAccessToken.from_dict(token_data)
id_token=ConsentRequestSessionIdToken.from_dict(id_token_data)
body = AcceptConsentRequest(
id_token = access_token
body = TheRequestPayloadUsedToAcceptAConsentRequest(
grant_scope= requested_scope,
grant_access_token_audience= requested_audiences,
remember= form.data['remember'],
remember_for= remember_for,
session= ConsentRequestSession(
session= ory_hydra_m.PassSessionDataToAConsentRequest(
access_token= access_token,
id_token= id_token
)
)
resp = await accept_consent_request.asyncio(_client=hydra_service.hydra_client,
resp = await accept_o_auth_2_consent_request.asyncio(_client=hydra_service.hydra_client,
json_body=body,
consent_challenge=consent_request.challenge)
if resp is None or isinstance( resp, GenericError):
@ -107,15 +98,15 @@ async def login() -> ResponseReturnValue:
login_challenge = request.args.get('login_challenge')
if login_challenge is None:
return 'login_challenge missing', 400
login_request = await get_login_request.asyncio(_client=hydra_service.hydra_client, login_challenge=login_challenge)
if login_request is None or isinstance( login_request, GenericError):
login_request = await get_o_auth_2_login_request.asyncio(_client=hydra_service.hydra_client, login_challenge=login_challenge)
if login_request is None or isinstance( login_request, ory_hydra_m.OAuth20RedirectBrowserTo):
logger.exception("could not fetch login request")
return redirect(url_for('frontend.index'))
if login_request.skip:
resp = await accept_login_request.asyncio(_client=hydra_service.hydra_client,
resp = await accept_o_auth_2_login_request.asyncio(_client=hydra_service.hydra_client,
login_challenge=login_challenge,
json_body=AcceptLoginRequest(subject=login_request.subject))
json_body=ory_hydra_m.HandledLoginRequestIsTheRequestPayloadUsedToAcceptALoginRequest(subject=login_request.subject))
if resp is None or isinstance( resp, GenericError):
return 'internal error, could not forward request', 503
@ -138,7 +129,7 @@ async def login_auth() -> ResponseReturnValue:
login_challenge = request.args.get('login_challenge')
if login_challenge is None:
return 'missing login_challenge, bad request', 400
login_request = await get_login_request.asyncio(_client=hydra_service.hydra_client, login_challenge=login_challenge)
login_request = await get_o_auth_2_login_request.asyncio(_client=hydra_service.hydra_client, login_challenge=login_challenge)
if login_request is None:
return redirect(url_for('frontend.index'))
@ -166,8 +157,8 @@ async def login_auth() -> ResponseReturnValue:
subject = user.id
user.last_login = datetime.now()
db.session.commit()
resp = await accept_login_request.asyncio(_client=hydra_service.hydra_client,
login_challenge=login_challenge, json_body=AcceptLoginRequest(
resp = await accept_o_auth_2_login_request.asyncio(_client=hydra_service.hydra_client,
login_challenge=login_challenge, json_body=ory_hydra_m.HandledLoginRequestIsTheRequestPayloadUsedToAcceptALoginRequest(
subject=subject,
remember=remember_me,
))
@ -198,7 +189,7 @@ async def logout() -> ResponseReturnValue:
if logout_challenge is None:
return 'invalid request, logout_challenge not set', 400
# TODO confirm
resp = await accept_logout_request.asyncio(_client=hydra_service.hydra_client, logout_challenge=logout_challenge)
resp = await accept_o_auth_2_logout_request.asyncio(_client=hydra_service.hydra_client, logout_challenge=logout_challenge)
if resp is None or isinstance( resp, GenericError):
return 'internal error, could not forward request', 503
return redirect(resp.redirect_to)

View File

@ -2,7 +2,7 @@
from authlib.integrations.base_client.errors import MissingTokenError, InvalidTokenError
from base64 import b64encode, b64decode
from fido2 import cbor
from fido2.webauthn import AttestationObject, AttestedCredentialData, AuthenticatorData
from fido2.webauthn import CollectedClientData, AttestationObject, AttestedCredentialData, AuthenticatorData, PublicKeyCredentialUserEntity
from flask import Blueprint, Response, redirect, request
from flask import current_app
from flask import jsonify, session, flash
@ -15,13 +15,13 @@ from datetime import timedelta
from base64 import b64decode
from flask.typing import ResponseReturnValue
from oauthlib.oauth2.rfc6749.errors import TokenExpiredError
from ory_hydra_client.api.admin import list_subject_consent_sessions, revoke_consent_sessions
from ory_hydra_client.api.o_auth_2 import list_o_auth_2_consent_sessions, revoke_o_auth_2_consent_sessions
from ory_hydra_client.models import GenericError
from urllib.parse import urlencode, parse_qs
from random import SystemRandom
import string
from collections.abc import Iterable
from typing import Optional
from typing import Optional, Mapping, Iterator, List
from ..model import db, User, SecurityUser, Totp, AppToken, WebauthnCredential
from ..form.frontend import ClientCertForm, TOTPForm, \
@ -247,9 +247,17 @@ def webauthn_delete_route(webauthn_id: str) -> ResponseReturnValue:
def webauthn_credentials(user: User) -> list[AttestedCredentialData]:
"""get and decode all credentials for given user"""
return [AttestedCredentialData.create(**cbor.decode(cred.credential_data)) for cred in user.webauthn_credentials]
def decode(creds: List[WebauthnCredential]) -> Iterator[AttestedCredentialData]:
for cred in creds:
data = cbor.decode(cred.credential_data)
if isinstance(data, Mapping):
yield AttestedCredentialData.create(**data)
return list(decode(user.webauthn_credentials))
def random_string(length=32) -> str:
@ -267,8 +275,9 @@ def webauthn_pkcco_route() -> ResponseReturnValue:
user_handle = random_string()
exclude_credentials = webauthn_credentials(user)
pkcco, state = webauthn.register_begin(
{'id': user_handle.encode('utf-8'), 'name': user.username, 'displayName': user.username},
exclude_credentials)
user=PublicKeyCredentialUserEntity(id=user_handle.encode('utf-8'), name=user.username, display_name=user.username),
credentials=exclude_credentials
)
session['webauthn_register_user_handle'] = user_handle
session['webauthn_register_state'] = state
return Response(b64encode(cbor.encode(pkcco)).decode('utf-8'), mimetype='text/plain')
@ -283,9 +292,11 @@ def webauthn_register_route() -> ResponseReturnValue:
if form.validate_on_submit():
try:
attestation = cbor.decode(b64decode(form.attestation.data))
if not isinstance(attestation, Mapping) or 'clientDataJSON' not in attestation or 'attestationObject' not in attestation:
return 'invalid attestion data', 400
auth_data = webauthn.register_complete(
session.pop('webauthn_register_state'),
ClientData(attestation['clientDataJSON']),
CollectedClientData(attestation['clientDataJSON']),
AttestationObject(attestation['attestationObject']))
db.session.add(WebauthnCredential(
@ -331,7 +342,7 @@ def password_change_post() -> ResponseReturnValue:
async def oauth2_tokens() -> ResponseReturnValue:
subject = oauth2.custom.get('/userinfo').json()['sub']
consent_sessions = await list_subject_consent_sessions.asyncio(subject=subject, _client=hydra_service.hydra_client)
consent_sessions = await list_o_auth_2_consent_sessions.asyncio(subject=subject, _client=hydra_service.hydra_client)
if consent_sessions is None or isinstance( consent_sessions, GenericError):
return 'internal error, could not fetch sessions', 500
return render_template(
@ -342,7 +353,7 @@ async def oauth2_tokens() -> ResponseReturnValue:
@frontend_views.route('/oauth2_token/<client_id>', methods=['DELETE'])
async def oauth2_token_revoke(client_id: str) -> ResponseReturnValue:
subject = oauth2.session.get('/userinfo').json()['sub']
await revoke_consent_sessions.asyncio( _client=hydra_service.hydra_client,
await revoke_o_auth_2_consent_sessions.asyncio_detailed( _client=hydra_service.hydra_client,
subject=subject,
client=client_id)