Source code for maeser.controllers.login_api

"""
Module for handling login and GitHub OAuth2 authorization controllers.

© 2024 Carson Bush, Blaine Freestone

This file is part of Maeser.

Maeser is free software: you can redistribute it and/or modify it under the terms of
the GNU Lesser General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.

Maeser 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
Maeser. If not, see <https://www.gnu.org/licenses/>.
"""

from flask import render_template, redirect, url_for, request, session
from flask_login import login_user, current_user
from urllib.parse import urljoin, urlparse

[docs] def controller(): """Raises NotImplementedError for unimplemented controllers. This function should be replaced with a specific login controller implementation, such as 'login_controller', 'github_authorize_controller', or 'github_auth_callback_controller'. """ raise NotImplementedError('Please import specify the login controller you want ("login_controller", "github_authorize_controller", "github_auth_callback_controller")')
[docs] def is_safe_url(target): """Checks if a URL is safe for redirection. Args: target (str): The target URL to check. Returns: bool: True if the URL is safe, False otherwise. """ ref_url = urlparse(request.host_url) test_url = urlparse(urljoin(request.host_url, target)) return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
[docs] def login_controller(auth_manager, app_name: str | None = None, main_logo_light: str | None = None, main_logo_dark: str | None = None, favicon: str | None = None): """Handles user login. Args: auth_manager (AuthManager): The authentication manager to handle user authentication. Returns: Response: The response object to render the login page or redirect. """ if current_user is not None and current_user.is_authenticated: return redirect('/') if request.method == 'POST': username = request.form.get('username', '') password = request.form.get('password', '') auth_method = request.form.get('authvalidator', 'invalid') if auth_method == 'invalid': return render_template( 'login.html', message='Invalid Authentication Method in Request', main_logo_light=main_logo_light, main_logo_dark=main_logo_dark, favicon=favicon, app_name=app_name if app_name else "Maeser", authenticators=auth_manager.authenticators, ) user = auth_manager.authenticate(auth_method, username, password) if user is None: return render_template( 'login.html', message='Authentication Failed', main_logo_light=main_logo_light, main_logo_dark=main_logo_dark, favicon=favicon, app_name=app_name if app_name else "Maeser", authenticators=auth_manager.authenticators, ) if not user.is_active: return render_template( 'login.html', message=f'User {user.full_id_name} is Banned', main_logo_light=main_logo_light, main_logo_dark=main_logo_dark, favicon=favicon, app_name=app_name if app_name else "Maeser", authenticators=auth_manager.authenticators, ) login_user(user) next_url = request.args.get('next') if not next_url or not is_safe_url(next_url): next_url = '/' return redirect(next_url) next_url = request.args.get('next') message = request.args.get('message', '') return render_template( 'login.html', message=message, next=next_url, main_logo_light=main_logo_light, main_logo_dark=main_logo_dark, favicon=favicon, app_name=app_name if app_name else "Maeser", authenticators=auth_manager.authenticators, )
[docs] def github_authorize_controller(current_user, github_authenticator): """Handles GitHub OAuth2 authorization. Args: current_user (User): The currently logged-in user. github_authenticator (GitHubAuthenticator): The GitHub authenticator to get OAuth2 info. Returns: Response: The response object to redirect to the OAuth2 provider. """ if not current_user.is_anonymous: return redirect('/') session['oauth2_state'], provider_url = github_authenticator.get_auth_info() session.modified = True print(f'OAuth2 state: {session["oauth2_state"]}') # Redirect the user to the OAuth2 provider authorization URL return redirect(provider_url)
[docs] def github_auth_callback_controller(current_user, auth_manager, app_name: str | None = None, main_logo_light: str | None = None, main_logo_dark: str | None = None, favicon: str | None = None, login_redirect: str = 'maeser.login'): if not current_user.is_anonymous: return redirect('/') # If there was an error before auth, render the login page with the error message if 'error' in request.args: print(f'An error occurred during the auth callback before authentication: {request.args}') error_message = request.args.get('error_description', 'Authentication failed') return redirect(url_for(login_redirect, message=error_message)) oauth_state = session.get('oauth2_state') print(f'OAuth2 state at callback: {oauth_state}') user = auth_manager.authenticate('github', request.args, oauth_state) if user is None: return redirect(url_for(login_redirect, message='GitHub Authentication Failed')) if not user.is_active: return redirect(url_for(login_redirect, message=f'User {user.full_id_name} is Banned')) login_user(user) return redirect('/')