From 3d08a99bd50f2249e7f8ac9a874349115b0f6c18 Mon Sep 17 00:00:00 2001 From: jacquesfize Date: Fri, 5 Jul 2024 09:46:08 +0200 Subject: [PATCH] clean code --- src/pypnusershub/auth/auth_manager.py | 13 ++-- src/pypnusershub/auth/authentication.py | 25 ++++++- .../auth/providers/cas_inpn_provider.py | 49 +++---------- .../auth/providers/github_provider.py | 16 +--- .../auth/providers/google_provider.py | 73 ------------------- .../auth/providers/openid_provider.py | 31 +++++--- .../auth/providers/usershub_provider.py | 18 ++--- src/pypnusershub/db/models.py | 36 +++------ src/pypnusershub/routes.py | 16 +--- 9 files changed, 85 insertions(+), 192 deletions(-) delete mode 100644 src/pypnusershub/auth/providers/google_provider.py diff --git a/src/pypnusershub/auth/auth_manager.py b/src/pypnusershub/auth/auth_manager.py index 19a47a9..0445057 100644 --- a/src/pypnusershub/auth/auth_manager.py +++ b/src/pypnusershub/auth/auth_manager.py @@ -1,10 +1,12 @@ -from .authentication import Authentication -from .providers import DefaultConfiguration -from pypnusershub.db.models import Provider import importlib + import sqlalchemy as sa +from pypnusershub.db.models import Provider from pypnusershub.env import db +from .authentication import Authentication +from .providers import DefaultConfiguration + class AuthManager: """ @@ -66,16 +68,13 @@ def add_provider( def init_app(self, app, prefix: str = "/auth") -> None: """ - Initializes the Flask application with the AuthManager. + Initializes the Flask application with the AuthManager. In addtion, it registers the authentification module blueprint. Parameters ---------- app : Flask The Flask application instance. - Returns - ------- - None """ from pypnusershub.routes import routes diff --git a/src/pypnusershub/auth/authentication.py b/src/pypnusershub/auth/authentication.py index cc9e81b..3c386b0 100644 --- a/src/pypnusershub/auth/authentication.py +++ b/src/pypnusershub/auth/authentication.py @@ -1,9 +1,8 @@ -from typing import Any, Union import logging - -from pypnusershub.db import models +from typing import Any, Union from marshmallow import Schema, fields +from pypnusershub.db import models log = logging.getLogger(__name__) @@ -143,7 +142,17 @@ def revoke(self) -> Any: log.warn("Revoke is not implemented.") pass - def configure(self, configuration: Union[dict, Any] = {}): + def configure(self, configuration: Union[dict, Any] = {}) -> None: + """ + Configure the authentication provider based on data in the configuration file. + + Parameters + ---------- + configuration : Union[dict, Any], optional + The configuration parameters. + Default is an empty dictionary. + + """ self.id_provider = configuration["id_provider"] for field in ["label", "logo", "login_url", "logout_url", "group_mapping"]: if field in configuration: @@ -151,4 +160,12 @@ def configure(self, configuration: Union[dict, Any] = {}): @staticmethod def configuration_schema() -> ProviderConfigurationSchema: + """ + Returns the marshmallow schema used to configure this authentication provider. + + Returns + ------- + ProviderConfigurationSchema + The schema used to configure this authentication provider. + """ return ProviderConfigurationSchema diff --git a/src/pypnusershub/auth/providers/cas_inpn_provider.py b/src/pypnusershub/auth/providers/cas_inpn_provider.py index fa218e4..c26c230 100644 --- a/src/pypnusershub/auth/providers/cas_inpn_provider.py +++ b/src/pypnusershub/auth/providers/cas_inpn_provider.py @@ -2,17 +2,10 @@ from typing import Any, Optional, Tuple, Union import xmltodict -from flask import ( - Response, - current_app, - make_response, - redirect, - render_template, - request, -) -from marshmallow import fields +from flask import Response, current_app, redirect, render_template, request from geonature.utils import utilsrequests from geonature.utils.errors import GeonatureApiError +from marshmallow import fields from pypnusershub.auth import Authentication, ProviderConfigurationSchema from pypnusershub.db import db, models from pypnusershub.routes import insert_or_update_organism, insert_or_update_role @@ -25,30 +18,6 @@ class CasAuthentificationError(GeonatureApiError): pass -AUTHENTIFICATION_CONFIG = { - "PROVIDER_NAME": "inpn", - "EXTERNAL_PROVIDER": True, -} - -CAS_AUTHENTIFICATION = True -CAS_PUBLIC = dict( - URL_LOGIN="https://inpn.mnhn.fr/auth/login", - URL_LOGOUT="https://inpn.mnhn.fr/auth/logout", - URL_VALIDATION="https://inpn.mnhn.fr/auth/serviceValidate", -) - -CAS_USER_WS = dict( - URL="https://inpn.mnhn.fr/authentication/information", - BASE_URL="https://inpn.mnhn.fr/authentication/", - ID="change_value", - PASSWORD="change_value", -) -USERS_CAN_SEE_ORGANISM_DATA = False - -ID_USER_SOCLE_1 = 1 -ID_USER_SOCLE_2 = 2 - - class AuthenficationCASINPN(Authentication): name = "CAS_INPN_PROVIDER" label = "INPN" @@ -157,16 +126,14 @@ def insert_user_and_org(self, info_user, id_provider): "email": info_user["email"], "active": True, } - user = insert_or_update_role( - models.User(**user_info), provider_name=self.id_provider - ) + user = insert_or_update_role(models.User(**user_info), provider_instance=self) if not user.groups: - if not USERS_CAN_SEE_ORGANISM_DATA or organism_id is None: + if not self.USERS_CAN_SEE_ORGANISM_DATA or organism_id is None: # group socle 1 - group_id = ID_USER_SOCLE_1 + group_id = self.ID_USER_SOCLE_1 else: # group socle 2 - group_id = ID_USER_SOCLE_2 + group_id = self.ID_USER_SOCLE_2 group = db.session.get(models.User, group_id) user.groups.append(group) return user @@ -187,11 +154,13 @@ class CASINPNConfiguration(ProviderConfigurationSchema): ) WS_ID = fields.String(required=True) WS_PASSWORD = fields.String(required=True) + USERS_CAN_SEE_ORGANISM_DATA = fields.Boolean(load_default=False) + ID_USER_SOCLE_1 = fields.Integer(load_default=7) + ID_USER_SOCLE_2 = fields.Integer(load_default=6) return CASINPNConfiguration def configure(self, configuration: Union[dict, Any]): super().configure(configuration) - print(configuration) for key in configuration: setattr(self, key, configuration[key]) diff --git a/src/pypnusershub/auth/providers/github_provider.py b/src/pypnusershub/auth/providers/github_provider.py index fd0aa58..214f03f 100644 --- a/src/pypnusershub/auth/providers/github_provider.py +++ b/src/pypnusershub/auth/providers/github_provider.py @@ -1,16 +1,11 @@ from typing import Union from authlib.integrations.flask_client import OAuth -from flask import ( - Response, - current_app, - url_for, -) +from flask import Response, current_app, url_for from pypnusershub.auth import Authentication -from pypnusershub.db import models, db +from pypnusershub.db import db, models from pypnusershub.routes import insert_or_update_role - oauth = OAuth(current_app) oauth.register( name="github", @@ -32,6 +27,7 @@ class GitHubAuthProvider(Authentication): login_url = "http://127.0.0.1:8000/auth/login/github" logout_url = "" logo = '' + name = "GITHUB_PROVIDER_CONFIG" def authenticate(self, *args, **kwargs) -> Union[Response, models.User]: redirect_uri = url_for( @@ -52,15 +48,11 @@ def authorize(self): "prenom_role": prenom, "nom_role": nom, "active": True, - "provider": "github", } - user_info = insert_or_update_role(new_user) + user_info = insert_or_update_role(new_user, self) user = db.session.get(models.User, user_info["id_role"]) if not user.groups: group = db.session.get(models.User, 2) # ADMIN for test user.groups.append(group) db.session.commit() return user - - -# Accueil : https://ginco2-preprod.mnhn.fr/ (URL publique) + http://ginco2-preprod.patnat.mnhn.fr/ (URL privée) diff --git a/src/pypnusershub/auth/providers/google_provider.py b/src/pypnusershub/auth/providers/google_provider.py deleted file mode 100644 index be356c3..0000000 --- a/src/pypnusershub/auth/providers/google_provider.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Any, Optional, Tuple, Union - -from authlib.integrations.flask_client import OAuth -from flask import ( - Response, - current_app, - url_for, -) -from marshmallow import Schema, fields - -from pypnusershub.auth import Authentication, ProviderConfigurationSchema, oauth -from pypnusershub.db import models, db -from pypnusershub.db.models import User -from pypnusershub.routes import insert_or_update_role -import sqlalchemy as sa - - -# TODO : à enlever : fonctionne avec OPENID_PROVIDER - -CONF_URL = "https://accounts.google.com/.well-known/openid-configuration" -oauth.register( - name="google", - server_metadata_url=CONF_URL, - client_kwargs={"scope": "openid email profile"}, -) - - -class GoogleAuthProvider(Authentication): - name = "GOOGLE_PROVIDER_CONFIG" - id_provider = "google" - label = "Google" - is_uh = False - login_url = "" - logout_url = "" - logo = '' - - def authenticate(self, *args, **kwargs) -> Union[Response, models.User]: - redirect_uri = url_for( - "auth.authorize", provider=self.id_provider, _external=True - ) - return oauth.google.authorize_redirect(redirect_uri) - - def authorize(self): - token = oauth.google.authorize_access_token() - user_info = token["userinfo"] - new_user = { - "identifiant": f"{user_info['given_name'].lower()}{user_info['family_name'].lower()}", - "email": user_info["email"], - "prenom_role": user_info["given_name"], - "nom_role": user_info["family_name"], - "active": True, - } - return insert_or_update_role(User(**new_user), provider_name=self.id_provider) - - return user - - @staticmethod - def configuration_schema() -> Optional[Tuple[str, ProviderConfigurationSchema]]: - class GoogleProviderConfiguration(ProviderConfigurationSchema): - GOOGLE_CLIENT_ID = fields.String(load_default="") - GOOGLE_CLIENT_SECRET = fields.String(load_default="") - - return GoogleProviderConfiguration - - def configure(self, configuration: Union[dict, Any]): - super().configure(configuration) - current_app.config["GOOGLE_CLIENT_ID"] = configuration["GOOGLE_CLIENT_ID"] - current_app.config["GOOGLE_CLIENT_SECRET"] = configuration[ - "GOOGLE_CLIENT_SECRET" - ] - - -# Accueil : https://ginco2-preprod.mnhn.fr/ (URL publique) + http://ginco2-preprod.patnat.mnhn.fr/ (URL privée) diff --git a/src/pypnusershub/auth/providers/openid_provider.py b/src/pypnusershub/auth/providers/openid_provider.py index 6c19c23..caa4b6a 100644 --- a/src/pypnusershub/auth/providers/openid_provider.py +++ b/src/pypnusershub/auth/providers/openid_provider.py @@ -1,23 +1,25 @@ -import requests - -from authlib.integrations.flask_client import OAuth -from marshmallow import Schema, fields -from typing import Any, Optional, Tuple, Union -from flask import Response, current_app, url_for, session -from werkzeug.exceptions import Unauthorized +from typing import Optional, Tuple, Union +import requests +from flask import Response, current_app, session, url_for +from marshmallow import fields from pypnusershub.auth import Authentication, ProviderConfigurationSchema, oauth -from pypnusershub.db import models, db +from pypnusershub.db import db, models from pypnusershub.routes import insert_or_update_role -from pypnusershub.auth.auth_manager import auth_manager +from werkzeug.exceptions import Unauthorized class OpenIDProvider(Authentication): + """ + OpenID provider authentication class. + + This class handle the authentication process with an OpenID provider. + + """ + name = "OPENID_PROVIDER_CONFIG" logo = '' is_uh = False - login_url = "" - logout_url = "" """ Name of the fields in the OpenID token that contains the groups info """ @@ -93,6 +95,13 @@ class OpenIDProviderConfiguration(ProviderConfigurationSchema): class OpenIDConnectProvider(OpenIDProvider): + """ + OpenID Connect provider authentication class. + + This class handle the authentication process with an OpenID Connect provider. + + """ + name = "OPENID_CONNECT_PROVIDER_CONFIG" def revoke(self): diff --git a/src/pypnusershub/auth/providers/usershub_provider.py b/src/pypnusershub/auth/providers/usershub_provider.py index 2312768..f3d71e2 100644 --- a/src/pypnusershub/auth/providers/usershub_provider.py +++ b/src/pypnusershub/auth/providers/usershub_provider.py @@ -1,19 +1,19 @@ -import requests -from typing import Any, Optional, Tuple, Union - -from marshmallow import Schema, fields - -from flask import request, Response, url_for, current_app, redirect -from werkzeug.exceptions import Unauthorized -from sqlalchemy import select +from typing import Optional, Tuple -from geonature.utils.env import db +import requests +from flask import request +from marshmallow import fields from pypnusershub.auth import Authentication, ProviderConfigurationSchema from pypnusershub.db.models import User from pypnusershub.routes import insert_or_update_role +from werkzeug.exceptions import Unauthorized class ExternalUsersHubAuthProvider(Authentication): + """ + Authentication provider for Flask application using UsersHub-authentification-module. + """ + name = "EXTERNAL_USERSHUB_PROVIDER_CONFIG" logo = '' diff --git a/src/pypnusershub/db/models.py b/src/pypnusershub/db/models.py index 15cadf2..456fded 100644 --- a/src/pypnusershub/db/models.py +++ b/src/pypnusershub/db/models.py @@ -1,6 +1,6 @@ # coding: utf8 -from __future__ import unicode_literals, print_function, absolute_import, division +from __future__ import absolute_import, division, print_function, unicode_literals from utils_flask_sqla.models import qfilter @@ -9,38 +9,29 @@ """ import hashlib + import bcrypt +import flask_sqlalchemy from bcrypt import checkpw -from os import environ -from importlib import import_module from packaging import version -from flask_sqlalchemy import SQLAlchemy -import flask_sqlalchemy - - if version.parse(flask_sqlalchemy.__version__) >= version.parse("3"): from flask_sqlalchemy.query import Query else: from flask_sqlalchemy import BaseQuery as Query - from flask import current_app from flask_login import UserMixin - +from pypnusershub.db.tools import DifferentPasswordError, NoPasswordError +from pypnusershub.env import db +from pypnusershub.utils import get_current_app_id +from sqlalchemy import ForeignKey, func, or_ +from sqlalchemy.dialects.postgresql import JSONB, UUID, array from sqlalchemy.ext.hybrid import hybrid_property -from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm import backref, relationship from sqlalchemy.orm.session import object_session -from sqlalchemy import Sequence, func, ForeignKey, or_ -from sqlalchemy.sql import select, func -from sqlalchemy.dialects.postgresql import UUID, JSONB, array from sqlalchemy.schema import FetchedValue - - -from pypnusershub.db.tools import NoPasswordError, DifferentPasswordError -from pypnusershub.env import db -from pypnusershub.utils import get_current_app_id - +from sqlalchemy.sql import func, select from utils_flask_sqla.serializers import serializable @@ -230,8 +221,8 @@ def is_public(self): def __repr__(self): return "".format(self.identifiant, self.id_role) - # def __str__(self): - # return self.identifiant or self.nom_complet + def __str__(self): + return self.identifiant or self.nom_complet @qfilter def filter_by_app(cls, code_app=None, **kwargs): @@ -422,9 +413,6 @@ class AppUser(db.Model): _password = db.Column("pass", db.Unicode) _password_plus = db.Column("pass_plus", db.Unicode) id_droit_max = db.Column(db.Integer, primary_key=True) - # user = db.relationship('User', backref='relations', lazy='joined') - # application = db.relationship('Application', - # backref='relations', lazy='joined') @property def password(self): diff --git a/src/pypnusershub/routes.py b/src/pypnusershub/routes.py index 042531b..3238498 100755 --- a/src/pypnusershub/routes.py +++ b/src/pypnusershub/routes.py @@ -1,18 +1,14 @@ # coding: utf8 - -from __future__ import absolute_import, division, print_function, unicode_literals -from typing import List - """ routes relatives aux application, utilisateurs et à l'authentification """ +from __future__ import absolute_import, division, print_function, unicode_literals + import datetime -import json import logging +from typing import List -import datetime -from flask_login import login_required, login_user, logout_user, current_user import sqlalchemy as sa from flask import ( Blueprint, @@ -20,7 +16,6 @@ current_app, g, jsonify, - make_response, redirect, request, session, @@ -31,9 +26,7 @@ from pypnusershub.db import db, models from pypnusershub.db.tools import encode_token from pypnusershub.schemas import OrganismeSchema, UserSchema -from pypnusershub.utils import get_current_app_id -from sqlalchemy.orm import exc -from werkzeug.exceptions import BadRequest, Forbidden, Unauthorized +from werkzeug.exceptions import Forbidden, Unauthorized log = logging.getLogger(__name__) # This module was originally designed as a submodule of designed @@ -94,7 +87,6 @@ def get_providers(): from itertools import chain property_name = [ - # "id_provider", "is_uh", "logo", "label",