ory hydra update to version 2
This commit is contained in:
parent
4a31250bca
commit
65ceb2abbd
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix"
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue