aboutsummaryrefslogtreecommitdiff
path: root/gn_auth/auth/authentication
diff options
context:
space:
mode:
Diffstat (limited to 'gn_auth/auth/authentication')
-rw-r--r--gn_auth/auth/authentication/oauth2/endpoints/introspection.py2
-rw-r--r--gn_auth/auth/authentication/oauth2/endpoints/revocation.py4
-rw-r--r--gn_auth/auth/authentication/oauth2/endpoints/utilities.py8
-rw-r--r--gn_auth/auth/authentication/oauth2/grants/jwt_bearer_grant.py12
-rw-r--r--gn_auth/auth/authentication/oauth2/models/jwt_bearer_token.py7
-rw-r--r--gn_auth/auth/authentication/oauth2/models/oauth2client.py26
-rw-r--r--gn_auth/auth/authentication/oauth2/models/oauth2token.py2
-rw-r--r--gn_auth/auth/authentication/oauth2/resource_server.py8
-rw-r--r--gn_auth/auth/authentication/oauth2/server.py1
-rw-r--r--gn_auth/auth/authentication/oauth2/views.py22
-rw-r--r--gn_auth/auth/authentication/users.py6
11 files changed, 60 insertions, 38 deletions
diff --git a/gn_auth/auth/authentication/oauth2/endpoints/introspection.py b/gn_auth/auth/authentication/oauth2/endpoints/introspection.py
index 200b25d..cebb3be 100644
--- a/gn_auth/auth/authentication/oauth2/endpoints/introspection.py
+++ b/gn_auth/auth/authentication/oauth2/endpoints/introspection.py
@@ -23,7 +23,7 @@ class IntrospectionEndpoint(_IntrospectionEndpoint):
CLIENT_AUTH_METHODS = ['client_secret_post']
def query_token(self, token_string: str, token_type_hint: str):
"""Query the token."""
- return _query_token(self, token_string, token_type_hint)
+ return _query_token(token_string, token_type_hint)
# pylint: disable=[no-self-use]
def introspect_token(self, token: OAuth2Token) -> dict:
diff --git a/gn_auth/auth/authentication/oauth2/endpoints/revocation.py b/gn_auth/auth/authentication/oauth2/endpoints/revocation.py
index 80922f1..e647214 100644
--- a/gn_auth/auth/authentication/oauth2/endpoints/revocation.py
+++ b/gn_auth/auth/authentication/oauth2/endpoints/revocation.py
@@ -1,9 +1,9 @@
"""Handle token revocation."""
from flask import current_app
+from gn_libs import sqlite3 as db
from authlib.oauth2.rfc7009 import RevocationEndpoint as _RevocationEndpoint
-from gn_auth.auth.db import sqlite3 as db
from gn_auth.auth.authentication.oauth2.models.oauth2token import (
save_token, OAuth2Token, revoke_token)
@@ -15,7 +15,7 @@ class RevocationEndpoint(_RevocationEndpoint):
CLIENT_AUTH_METHODS = ['client_secret_post']
def query_token(self, token_string: str, token_type_hint: str):
"""Query the token."""
- return _query_token(self, token_string, token_type_hint)
+ return _query_token(token_string, token_type_hint)
def revoke_token(self, token: OAuth2Token, request):
"""Revoke token `token`."""
diff --git a/gn_auth/auth/authentication/oauth2/endpoints/utilities.py b/gn_auth/auth/authentication/oauth2/endpoints/utilities.py
index 08b2a3b..82fd8e1 100644
--- a/gn_auth/auth/authentication/oauth2/endpoints/utilities.py
+++ b/gn_auth/auth/authentication/oauth2/endpoints/utilities.py
@@ -1,16 +1,14 @@
"""endpoint utilities"""
-from typing import Any, Optional
+from typing import Optional
from flask import current_app
from pymonad.maybe import Nothing
+from gn_libs import sqlite3 as db
-from gn_auth.auth.db import sqlite3 as db
from gn_auth.auth.authentication.oauth2.models.oauth2token import (
OAuth2Token, token_by_access_token, token_by_refresh_token)
-def query_token(# pylint: disable=[unused-argument]
- endpoint_object: Any, token_str: str, token_type_hint) -> Optional[
- OAuth2Token]:
+def query_token(token_str: str, token_type_hint) -> Optional[OAuth2Token]:
"""Retrieve the token from the database."""
def __identity__(val):
"""Identity function."""
diff --git a/gn_auth/auth/authentication/oauth2/grants/jwt_bearer_grant.py b/gn_auth/auth/authentication/oauth2/grants/jwt_bearer_grant.py
index c802091..63f979c 100644
--- a/gn_auth/auth/authentication/oauth2/grants/jwt_bearer_grant.py
+++ b/gn_auth/auth/authentication/oauth2/grants/jwt_bearer_grant.py
@@ -1,9 +1,8 @@
"""JWT as Authorisation Grant"""
import uuid
import time
-
+import logging
from typing import Optional
-from flask import current_app as app
from authlib.jose import jwt
from authlib.common.encoding import to_native
@@ -12,12 +11,17 @@ from authlib.oauth2.rfc7523.jwt_bearer import JWTBearerGrant as _JWTBearerGrant
from authlib.oauth2.rfc7523.token import (
JWTBearerTokenGenerator as _JWTBearerTokenGenerator)
-from gn_auth.debug import __pk__
+from gn_libs.debug import make_peeker
+
from gn_auth.auth.db.sqlite3 import with_db_connection
from gn_auth.auth.authentication.users import User, user_by_id
from gn_auth.auth.authentication.oauth2.models.oauth2client import OAuth2Client
+logger = logging.getLogger(__name__)
+__pk__ = make_peeker(logger)
+
+
class JWTBearerTokenGenerator(_JWTBearerTokenGenerator):
"""
A JSON Web Token formatted bearer token generator for jwt-bearer grant type.
@@ -149,6 +153,6 @@ class JWTBearerGrant(_JWTBearerGrant):
include_refresh_token=self.request.client.check_grant_type(
"refresh_token")
)
- app.logger.debug('Issue token %r to %r', token, self.request.client)
+ logger.debug('Issue token %r to %r', token, self.request.client)
self.save_token(token)
return 200, token, self.TOKEN_RESPONSE_HEADER
diff --git a/gn_auth/auth/authentication/oauth2/models/jwt_bearer_token.py b/gn_auth/auth/authentication/oauth2/models/jwt_bearer_token.py
index 71769e1..39249ba 100644
--- a/gn_auth/auth/authentication/oauth2/models/jwt_bearer_token.py
+++ b/gn_auth/auth/authentication/oauth2/models/jwt_bearer_token.py
@@ -3,9 +3,10 @@ import uuid
import time
from typing import Optional
+from flask import current_app as app
from authlib.oauth2.rfc7523 import JWTBearerToken as _JWTBearerToken
+from gn_libs.sqlite3 import with_db_connection
-from gn_auth.auth.db.sqlite3 import with_db_connection
from gn_auth.auth.authentication.users import user_by_id
from gn_auth.auth.authentication.oauth2.models.oauth2client import (
client as fetch_client)
@@ -19,8 +20,10 @@ class JWTBearerToken(_JWTBearerToken):
# OAuth2Client is a dataclass
super().__init__(payload, header, options, params)
self.user = with_db_connection(
- lambda conn:user_by_id(conn, uuid.UUID(payload["sub"])))
+ app.config["AUTH_DB"],
+ lambda conn: user_by_id(conn, uuid.UUID(payload["sub"])))
self.client = with_db_connection(
+ app.config["AUTH_DB"],
lambda conn: fetch_client(
conn, uuid.UUID(payload["oauth2_client_id"])
)
diff --git a/gn_auth/auth/authentication/oauth2/models/oauth2client.py b/gn_auth/auth/authentication/oauth2/models/oauth2client.py
index 1639e2e..818399d 100644
--- a/gn_auth/auth/authentication/oauth2/models/oauth2client.py
+++ b/gn_auth/auth/authentication/oauth2/models/oauth2client.py
@@ -1,20 +1,21 @@
"""OAuth2 Client model."""
import json
+import logging
import datetime
from uuid import UUID
+from urllib.parse import urlparse
from functools import cached_property
from dataclasses import asdict, dataclass
from typing import Any, Sequence, Optional
import requests
-from flask import current_app as app
from requests.exceptions import JSONDecodeError
from authlib.jose import KeySet, JsonWebKey
from authlib.oauth2.rfc6749 import ClientMixin
from pymonad.maybe import Just, Maybe, Nothing
+from gn_libs import sqlite3 as db
+from gn_libs.debug import make_peeker
-from gn_auth.debug import __pk__
-from gn_auth.auth.db import sqlite3 as db
from gn_auth.auth.errors import NotFoundError
from gn_auth.auth.authentication.users import (User,
fetch_users,
@@ -22,6 +23,10 @@ from gn_auth.auth.authentication.users import (User,
same_password)
+logger = logging.getLogger(__name__)
+__pk__ = make_peeker(logger)
+
+
@dataclass(frozen=True)
class OAuth2Client(ClientMixin):
"""
@@ -65,7 +70,7 @@ class OAuth2Client(ClientMixin):
jwksuri = self.client_metadata.get("public-jwks-uri")
__pk__(f"PUBLIC JWKs link for client {self.client_id}", jwksuri)
if not bool(jwksuri):
- app.logger.debug("No Public JWKs URI set for client!")
+ logger.debug("No Public JWKs URI set for client!")
return KeySet([])
try:
## IMPORTANT: This can cause a deadlock if the client is working in
@@ -77,13 +82,12 @@ class OAuth2Client(ClientMixin):
timeout=300,
allow_redirects=True).json()["jwks"]])
except requests.ConnectionError as _connerr:
- app.logger.debug(
+ logger.debug(
"Could not connect to provided URI: %s", jwksuri, exc_info=True)
except JSONDecodeError as _jsonerr:
- app.logger.debug(
- "Could not convert response to JSON", exc_info=True)
+ logger.debug("Could not convert response to JSON", exc_info=True)
except Exception as _exc:# pylint: disable=[broad-except]
- app.logger.debug(
+ logger.debug(
"Error retrieving the JWKs for the client.", exc_info=True)
return KeySet([])
@@ -135,7 +139,9 @@ class OAuth2Client(ClientMixin):
"""
Check whether the given `redirect_uri` is one of the expected ones.
"""
- return redirect_uri in self.redirect_uris
+ uri = urlparse(redirect_uri)._replace(
+ query="")._replace(fragment="").geturl()
+ return uri in self.redirect_uris
@cached_property
def response_types(self) -> Sequence[str]:
@@ -292,6 +298,8 @@ def delete_client(
cursor.execute("DELETE FROM authorisation_code WHERE client_id=?",
params)
cursor.execute("DELETE FROM oauth2_tokens WHERE client_id=?", params)
+ cursor.execute("DELETE FROM jwt_refresh_tokens WHERE client_id=?",
+ params)
cursor.execute("DELETE FROM oauth2_clients WHERE client_id=?", params)
return the_client
diff --git a/gn_auth/auth/authentication/oauth2/models/oauth2token.py b/gn_auth/auth/authentication/oauth2/models/oauth2token.py
index 6ec5c3d..eb13f43 100644
--- a/gn_auth/auth/authentication/oauth2/models/oauth2token.py
+++ b/gn_auth/auth/authentication/oauth2/models/oauth2token.py
@@ -8,8 +8,8 @@ from typing import Optional
from authlib.oauth2.rfc6749 import TokenMixin
from pymonad.tools import monad_from_none_or_value
from pymonad.maybe import Just, Maybe, Nothing
+from gn_libs import sqlite3 as db
-from gn_auth.auth.db import sqlite3 as db
from gn_auth.auth.errors import NotFoundError
from gn_auth.auth.authentication.users import User, user_by_id
diff --git a/gn_auth/auth/authentication/oauth2/resource_server.py b/gn_auth/auth/authentication/oauth2/resource_server.py
index 8ecf923..c09f6b6 100644
--- a/gn_auth/auth/authentication/oauth2/resource_server.py
+++ b/gn_auth/auth/authentication/oauth2/resource_server.py
@@ -1,4 +1,5 @@
"""Protect the resources endpoints"""
+import logging
from datetime import datetime, timezone, timedelta
from flask import current_app as app
@@ -8,14 +9,17 @@ from authlib.oauth2.rfc6750 import BearerTokenValidator as _BearerTokenValidator
from authlib.oauth2.rfc7523 import (
JWTBearerTokenValidator as _JWTBearerTokenValidator)
from authlib.integrations.flask_oauth2 import ResourceProtector
+from gn_libs import sqlite3 as db
-from gn_auth.auth.db import sqlite3 as db
from gn_auth.auth.jwks import list_jwks, jwks_directory
from gn_auth.auth.authentication.oauth2.models.jwt_bearer_token import (
JWTBearerToken)
from gn_auth.auth.authentication.oauth2.models.oauth2token import (
token_by_access_token)
+logger = logging.getLogger(__name__)
+
+
class BearerTokenValidator(_BearerTokenValidator):
"""Extends `authlib.oauth2.rfc6750.BearerTokenValidator`"""
def authenticate_token(self, token_string: str):
@@ -66,7 +70,7 @@ class JWTBearerTokenValidator(_JWTBearerTokenValidator):
claims.validate()
return claims
except JoseError as error:
- app.logger.debug('Authenticate token failed. %r', error)
+ logger.debug('Authenticate token failed. %r', error)
return None
diff --git a/gn_auth/auth/authentication/oauth2/server.py b/gn_auth/auth/authentication/oauth2/server.py
index 8ac5106..fd45b63 100644
--- a/gn_auth/auth/authentication/oauth2/server.py
+++ b/gn_auth/auth/authentication/oauth2/server.py
@@ -129,6 +129,7 @@ def setup_oauth2_server(app: Flask) -> None:
server.register_token_generator(
"urn:ietf:params:oauth:grant-type:jwt-bearer", jwttokengenerator)
server.register_token_generator("refresh_token", jwttokengenerator)
+ server.register_token_generator("password", jwttokengenerator)
server.register_grant(RefreshTokenGrant)
# register endpoints
diff --git a/gn_auth/auth/authentication/oauth2/views.py b/gn_auth/auth/authentication/oauth2/views.py
index 0e2c4eb..f73a712 100644
--- a/gn_auth/auth/authentication/oauth2/views.py
+++ b/gn_auth/auth/authentication/oauth2/views.py
@@ -1,5 +1,6 @@
"""Endpoints for the oauth2 server"""
import uuid
+import logging
import traceback
from urllib.parse import urlparse
@@ -15,9 +16,9 @@ from flask import (
Blueprint,
render_template,
current_app as app)
+from gn_libs import sqlite3 as db
+from gn_libs.sqlite3 import with_db_connection
-from gn_auth.auth.db import sqlite3 as db
-from gn_auth.auth.db.sqlite3 import with_db_connection
from gn_auth.auth.jwks import jwks_directory, list_jwks
from gn_auth.auth.errors import NotFoundError, ForbiddenAccess
from gn_auth.auth.authentication.users import valid_login, user_by_email
@@ -27,8 +28,10 @@ from .endpoints.revocation import RevocationEndpoint
from .endpoints.introspection import IntrospectionEndpoint
+logger = logging.getLogger(__name__)
auth = Blueprint("auth", __name__)
+
@auth.route("/delete-client/<uuid:client_id>", methods=["GET", "POST"])
def delete_client(client_id: uuid.UUID):
"""Delete an OAuth2 client."""
@@ -44,7 +47,7 @@ def authorise():
or str(uuid.uuid4()))
client = server.query_client(client_id)
if not bool(client):
- flash("Invalid OAuth2 client.", "alert-danger")
+ flash("Invalid OAuth2 client.", "alert alert-danger")
if request.method == "GET":
def __forgot_password_table_exists__(conn):
@@ -65,6 +68,7 @@ def authorise():
redirect_uri=request.args["redirect_uri"],
source_uri=f"{_src.scheme}://{_src.netloc}/",
display_forgot_password=with_db_connection(
+ app.config["AUTH_DB"],
__forgot_password_table_exists__))
form = request.form
@@ -88,18 +92,18 @@ def authorise():
email=email["email"]),
code=307)
return server.create_authorization_response(request=request, grant_user=user)
- flash(email_passwd_msg, "alert-danger")
+ flash(email_passwd_msg, "alert alert-danger")
return redirect_response # type: ignore[return-value]
except EmailNotValidError as _enve:
- app.logger.debug(traceback.format_exc())
- flash(email_passwd_msg, "alert-danger")
+ logger.debug(traceback.format_exc())
+ flash(email_passwd_msg, "alert alert-danger")
return redirect_response # type: ignore[return-value]
except NotFoundError as _nfe:
- app.logger.debug(traceback.format_exc())
- flash(email_passwd_msg, "alert-danger")
+ logger.debug(traceback.format_exc())
+ flash(email_passwd_msg, "alert alert-danger")
return redirect_response # type: ignore[return-value]
- return with_db_connection(__authorise__)
+ return with_db_connection(app.config["AUTH_DB"], __authorise__)
except InvalidClientError as ice:
return render_template(
"oauth2/oauth2_error.html", error=ice), ice.status_code
diff --git a/gn_auth/auth/authentication/users.py b/gn_auth/auth/authentication/users.py
index 140ce36..5c6a430 100644
--- a/gn_auth/auth/authentication/users.py
+++ b/gn_auth/auth/authentication/users.py
@@ -1,14 +1,14 @@
"""User-specific code and data structures."""
import datetime
-from typing import Tuple
+from typing import Tuple, Union
from uuid import UUID, uuid4
from dataclasses import dataclass
import sqlite3
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
+from gn_libs import sqlite3 as db
-from gn_auth.auth.db import sqlite3 as db
from gn_auth.auth.errors import NotFoundError
@@ -26,7 +26,7 @@ class User:
return self.user_id
@staticmethod
- def from_sqlite3_row(row: sqlite3.Row):
+ def from_sqlite3_row(row: Union[sqlite3.Row, dict]):
"""Generate a user from a row in an SQLite3 resultset"""
return User(user_id=UUID(row["user_id"]),
email=row["email"],