update fixes, ...
This commit is contained in:
parent
c6042973fe
commit
1947a6f24a
31 changed files with 3395 additions and 145 deletions
|
@ -9,6 +9,7 @@ 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 typing import Optional
|
||||
from collections.abc import Iterable
|
||||
import logging
|
||||
|
||||
from ..model import db, User
|
||||
|
@ -44,19 +45,19 @@ async def index() -> ResponseReturnValue:
|
|||
|
||||
@admin_views.route('/user', methods=['GET'])
|
||||
async def users():
|
||||
users = User.query.all()
|
||||
users = User.query.all() # type: Iterable[User]
|
||||
return render_template('admin/users.html.j2', users=users)
|
||||
|
||||
|
||||
@admin_views.route('/registrations', methods=['GET'])
|
||||
def registrations() -> ResponseReturnValue:
|
||||
users = User.query.filter_by(enabled=False).all()
|
||||
users = User.query.filter_by(enabled=False).all() # type: Iterable[User]
|
||||
return render_template('admin/registrations.html.j2', users=users)
|
||||
|
||||
|
||||
@admin_views.route('/registration/<registration_id>', methods=['DELETE'])
|
||||
def registration_delete(registration_id) -> ResponseReturnValue:
|
||||
user = User.query.get(registration_id)
|
||||
user = User.query.get(registration_id) # type: Optional[User]
|
||||
if user is None:
|
||||
return jsonify({}), 404
|
||||
db.session.delete(user)
|
||||
|
@ -66,7 +67,9 @@ def registration_delete(registration_id) -> ResponseReturnValue:
|
|||
|
||||
@admin_views.route('/registration/<registration_id>', methods=['PUT'])
|
||||
def registration_accept(registration_id) -> ResponseReturnValue:
|
||||
user = User.query.get(registration_id)
|
||||
user = User.query.get(registration_id) # type: Optional[User]
|
||||
if user is None:
|
||||
return jsonify({'message':'user not found'}), 404
|
||||
user.enabled = True
|
||||
db.session.commit()
|
||||
return jsonify({})
|
||||
|
|
|
@ -7,11 +7,15 @@ from flask.templating import render_template
|
|||
from flask.typing import ResponseReturnValue
|
||||
|
||||
from flask import Blueprint, render_template, request, url_for
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
import logging
|
||||
import httpx
|
||||
import secrets
|
||||
|
||||
from ..model import User
|
||||
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.models import GenericError
|
||||
|
||||
|
@ -40,39 +44,50 @@ def user_list() -> ResponseReturnValue:
|
|||
|
||||
@api_views.route('/introspect', methods=['POST'])
|
||||
def introspect() -> ResponseReturnValue:
|
||||
token = request.form['token']
|
||||
logger.error(f'debug token: {token}')
|
||||
token = request.form['token'] # type: Optional[str]
|
||||
resp = httpx.post("https://hydra.cloud.tux.ac/oauth2/introspect", data={'token':token})
|
||||
#if token_info is None or isinstance(token_info, GenericError):
|
||||
if resp.status_code != 200:
|
||||
return jsonify({}), 500
|
||||
token_info = resp.json()
|
||||
#token_info = introspect_o_auth_2_token.sync(_client=hydra_service, token=token)
|
||||
|
||||
if not token_info['active']:
|
||||
return jsonify({'active': False})
|
||||
token_info['email'] = token_info['ext']['email']
|
||||
|
||||
logger.error(f'debug: {token_info}')
|
||||
|
||||
return jsonify(token_info)
|
||||
|
||||
|
||||
@api_views.route('email/login', methods=['POST'])
|
||||
def email_login() -> ResponseReturnValue:
|
||||
logger.error(f'{request}')
|
||||
logger.error(f'{request.headers}')
|
||||
@api_views.route('/login/<service_name>', methods=['POST'])
|
||||
def email_login(service_name: str) -> ResponseReturnValue:
|
||||
if service_name not in lenticular_services:
|
||||
return '', 404
|
||||
service = lenticular_services[service_name]
|
||||
|
||||
if not request.is_json:
|
||||
return jsonify({}), 400
|
||||
req_payload = request.get_json()
|
||||
logger.debug(f'{req_payload}')
|
||||
req_payload = request.get_json() # type: Any
|
||||
|
||||
if not isinstance(req_payload, dict):
|
||||
return 'bad request', 400
|
||||
|
||||
password = req_payload["password"]
|
||||
username = req_payload["username"]
|
||||
|
||||
if password == "123456":
|
||||
return jsonify({})
|
||||
if '@' in username:
|
||||
username = username.split('@')[0]
|
||||
|
||||
user = User.query.filter_by(username=username.lower()).first() # type: Optional[User]
|
||||
if user is None:
|
||||
logger.warning(f'login with invalid username')
|
||||
return jsonify({}), 403
|
||||
|
||||
for app_token in user.get_tokens_by_service(service):
|
||||
if secrets.compare_digest(password, app_token.token):
|
||||
app_token.last_used = datetime.now()
|
||||
db.session.commit()
|
||||
return jsonify({'username': user.username}), 200
|
||||
|
||||
logger.warning(f'login with invalid password for {username}')
|
||||
return jsonify({}), 403
|
||||
|
||||
|
|
|
@ -60,7 +60,9 @@ async def consent() -> ResponseReturnValue:
|
|||
requested_audiences = consent_request.requested_access_token_audience
|
||||
|
||||
if form.validate_on_submit() or consent_request.skip:
|
||||
user = User.query.get(consent_request.subject)
|
||||
user = User.query.get(consent_request.subject) # type: Optional[User]
|
||||
if user is None:
|
||||
return 'internal error', 500
|
||||
token_data = {
|
||||
'name': str(user.username),
|
||||
'preferred_username': str(user.username),
|
||||
|
@ -118,7 +120,7 @@ async def login() -> ResponseReturnValue:
|
|||
return redirect(resp.redirect_to)
|
||||
form = LoginForm()
|
||||
if form.validate_on_submit():
|
||||
user = User.query.filter_by(username=form.data['name']).first()
|
||||
user = User.query.filter_by(username=form.data['name']).first() # type: Optional[User]
|
||||
if user:
|
||||
session['username'] = str(user.username)
|
||||
else:
|
||||
|
@ -141,7 +143,7 @@ async def login_auth() -> ResponseReturnValue:
|
|||
if 'username' not in session:
|
||||
return redirect(url_for('auth.login'))
|
||||
auth_forms = {}
|
||||
user = User.query.filter_by(username=session['username']).first()
|
||||
user = User.query.filter_by(username=session['username']).first() # Optional[User]
|
||||
for auth_provider in AUTH_PROVIDER_LIST:
|
||||
form = auth_provider.get_form()
|
||||
if auth_provider.get_name() not in session['auth_providers'] and\
|
||||
|
@ -178,7 +180,7 @@ async def login_auth() -> ResponseReturnValue:
|
|||
def webauthn_pkcro_route():
|
||||
"""login webauthn pkcro route"""
|
||||
|
||||
user = User.query.filter(User.id == session.get('webauthn_login_user_id')).one_or_none()
|
||||
user = User.query.filter(User.id == session.get('webauthn_login_user_id')).one() #type: User
|
||||
form = ButtonForm()
|
||||
if user and form.validate_on_submit():
|
||||
pkcro, state = webauthn.authenticate_begin(webauthn_credentials(user))
|
||||
|
|
|
@ -21,11 +21,13 @@ 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 ..model import db, User, SecurityUser, Totp, WebauthnCredential
|
||||
from ..model import db, User, SecurityUser, Totp, AppToken, WebauthnCredential
|
||||
from ..form.frontend import ClientCertForm, TOTPForm, \
|
||||
TOTPDeleteForm, PasswordChangeForm, WebauthnRegisterForm
|
||||
TOTPDeleteForm, PasswordChangeForm, WebauthnRegisterForm, \
|
||||
AppTokenForm, AppTokenDeleteForm
|
||||
from ..form.base import ButtonForm
|
||||
from ..auth_providers import PasswordAuthProvider
|
||||
from .auth import webauthn
|
||||
|
@ -111,6 +113,8 @@ def revoke_client_cert(service_name, serial_number) -> ResponseReturnValue:
|
|||
'/client_cert/<service_name>/new',
|
||||
methods=['GET', 'POST'])
|
||||
def client_cert_new(service_name) -> ResponseReturnValue:
|
||||
if service_name not in lenticular_services:
|
||||
return '', 404
|
||||
service = lenticular_services[service_name]
|
||||
form = ClientCertForm()
|
||||
if form.validate_on_submit():
|
||||
|
@ -139,16 +143,51 @@ def client_cert_new(service_name) -> ResponseReturnValue:
|
|||
|
||||
@frontend_views.route('/app_token')
|
||||
def app_token() -> ResponseReturnValue:
|
||||
delete_form = TOTPDeleteForm()
|
||||
return render_template('frontend/app_token.html.j2', delete_form=delete_form)
|
||||
delete_form = AppTokenDeleteForm()
|
||||
form = ClientCertForm()
|
||||
return render_template('frontend/app_token.html.j2',
|
||||
delete_form=delete_form,
|
||||
services=lenticular_services)
|
||||
|
||||
@frontend_views.route('/app_token/<service_name>/new')
|
||||
@frontend_views.route('/app_token/<service_name>/new', methods=['GET','POST'])
|
||||
def app_token_new(service_name: str) -> ResponseReturnValue:
|
||||
return
|
||||
if service_name not in lenticular_services:
|
||||
return '', 404
|
||||
service = lenticular_services[service_name]
|
||||
form = AppTokenForm()
|
||||
|
||||
@frontend_views.route('/app_token/<service_name>/<token_name>')
|
||||
def app_token_delete(service_name: str, token_name: str) -> ResponseReturnValue:
|
||||
return
|
||||
if form.validate_on_submit():
|
||||
app_token = AppToken.new(service)
|
||||
form.populate_obj(app_token)
|
||||
# check for duplicate names
|
||||
for user_app_token in current_user.app_tokens:
|
||||
if user_app_token.name == app_token.name:
|
||||
return 'name already exist', 400
|
||||
current_user.app_tokens.append(app_token)
|
||||
db.session.commit()
|
||||
return render_template('frontend/app_token_new_show.html.j2', service=service, app_token=app_token)
|
||||
|
||||
|
||||
return render_template('frontend/app_token_new.html.j2',
|
||||
form=form,
|
||||
service=service)
|
||||
|
||||
@frontend_views.route('/app_token/<service_name>/<app_token_name>', methods=["POST"])
|
||||
def app_token_delete(service_name: str, app_token_name: str) -> ResponseReturnValue:
|
||||
form = AppTokenDeleteForm()
|
||||
|
||||
if service_name not in lenticular_services:
|
||||
return '', 404
|
||||
|
||||
service = lenticular_services[service_name]
|
||||
if form.validate_on_submit():
|
||||
app_token = current_user.get_token(service, app_token_name)
|
||||
if app_token is None:
|
||||
return 'not found', 404
|
||||
db.session.delete(app_token)
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for('frontend.app_token'))
|
||||
|
||||
@frontend_views.route('/totp')
|
||||
def totp() -> ResponseReturnValue:
|
||||
|
@ -178,7 +217,7 @@ def totp_new() -> ResponseReturnValue:
|
|||
|
||||
@frontend_views.route('/totp/<totp_name>/delete', methods=['GET', 'POST'])
|
||||
def totp_delete(totp_name) -> ResponseReturnValue:
|
||||
totp = Totp.query.filter(Totp.name == totp_name).first()
|
||||
totp = Totp.query.filter(Totp.name == totp_name).first() # type: Optional[Totp]
|
||||
db.session.delete(totp)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -190,7 +229,7 @@ def totp_delete(totp_name) -> ResponseReturnValue:
|
|||
def webauthn_list_route() -> ResponseReturnValue:
|
||||
"""list registered credentials for current user"""
|
||||
|
||||
creds = WebauthnCredential.query.all()
|
||||
creds = WebauthnCredential.query.all() # type: Iterable[WebauthnCredential]
|
||||
return render_template('frontend/webauthn_list.html', creds=creds, button_form=ButtonForm())
|
||||
|
||||
|
||||
|
@ -200,7 +239,7 @@ def webauthn_delete_route(webauthn_id: str) -> ResponseReturnValue:
|
|||
|
||||
form = ButtonForm()
|
||||
if form.validate_on_submit():
|
||||
cred = WebauthnCredential.query.filter(WebauthnCredential.id == webauthn_id).one()
|
||||
cred = WebauthnCredential.query.filter(WebauthnCredential.id == webauthn_id).one() # type: WebauthnCredential
|
||||
db.session.delete(cred)
|
||||
db.session.commit()
|
||||
return redirect(url_for('app.webauthn_list_route'))
|
||||
|
@ -223,7 +262,9 @@ def random_string(length=32) -> str:
|
|||
def webauthn_pkcco_route() -> ResponseReturnValue:
|
||||
"""get publicKeyCredentialCreationOptions"""
|
||||
|
||||
user = User.query.get(current_user.id)
|
||||
user = User.query.get(current_user.id) #type: Optional[User]
|
||||
if user is None:
|
||||
return 'internal error', 500
|
||||
user_handle = random_string()
|
||||
exclude_credentials = webauthn_credentials(user)
|
||||
pkcco, state = webauthn.register_begin(
|
||||
|
@ -238,7 +279,7 @@ def webauthn_pkcco_route() -> ResponseReturnValue:
|
|||
def webauthn_register_route() -> ResponseReturnValue:
|
||||
"""register credential for current user"""
|
||||
|
||||
user = User.query.get(current_user.id)
|
||||
user = current_user # type: User
|
||||
form = WebauthnRegisterForm()
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
|
@ -279,11 +320,11 @@ def password_change_post() -> ResponseReturnValue:
|
|||
current_user, password_old):
|
||||
return jsonify(
|
||||
{'errors': {'password_old': 'Old Password is invalid'}})
|
||||
resp = current_user.change_password(password_new)
|
||||
if resp:
|
||||
return jsonify({})
|
||||
else:
|
||||
return jsonify({'errors': {'internal': 'internal server errror'}})
|
||||
|
||||
current_user.change_password(password_new)
|
||||
logger.info(f"user {current_user.username} changed password")
|
||||
db.session.commit()
|
||||
return jsonify({})
|
||||
return jsonify({'errors': form.errors})
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from flask_login import login_user, logout_user, current_user
|
|||
from flask.typing import ResponseReturnValue
|
||||
from flask_login import LoginManager
|
||||
from typing import Optional
|
||||
from werkzeug.wrappers.response import Response as WerkzeugResponse
|
||||
import logging
|
||||
|
||||
from ..model import User, SecurityUser
|
||||
|
@ -28,7 +29,7 @@ def redirect_login() -> ResponseReturnValue:
|
|||
session['next_url'] = request.path
|
||||
redirect_uri = url_for('oauth2.authorized', _external=True)
|
||||
response = oauth2.custom.authorize_redirect(redirect_uri)
|
||||
if isinstance(response, Response):
|
||||
if not isinstance(response, WerkzeugResponse):
|
||||
raise RuntimeError("invalid redirect")
|
||||
return response
|
||||
|
||||
|
@ -44,7 +45,7 @@ def authorized() -> ResponseReturnValue:
|
|||
return 'bad request', 400
|
||||
session['token'] = token
|
||||
userinfo = oauth2.custom.get('/userinfo').json()
|
||||
user = User.query.get(str(userinfo["sub"]))
|
||||
user = User.query.get(str(userinfo["sub"])) # type: Optional[User]
|
||||
if user is None:
|
||||
return "user not found", 404
|
||||
logger.info(f"user `{user.username}` successfully logged in")
|
||||
|
@ -60,14 +61,14 @@ def authorized() -> ResponseReturnValue:
|
|||
def login() -> ResponseReturnValue:
|
||||
redirect_uri = url_for('.authorized', _external=True)
|
||||
response = oauth2.custom.authorize_redirect(redirect_uri)
|
||||
#if type(response) != Response:
|
||||
# raise RuntimeError("invalid redirect")
|
||||
if not isinstance(response, WerkzeugResponse):
|
||||
raise RuntimeError("invalid redirect")
|
||||
return response
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def user_loader(username) -> Optional[User]:
|
||||
user = User.query.filter_by(username=username).first()
|
||||
user = User.query.filter_by(username=username).first() # type: Optional[User]
|
||||
if isinstance(user, User):
|
||||
return user
|
||||
else:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue