aboutsummaryrefslogtreecommitdiff
path: root/gn_auth/auth/authorisation/users
diff options
context:
space:
mode:
Diffstat (limited to 'gn_auth/auth/authorisation/users')
-rw-r--r--gn_auth/auth/authorisation/users/admin/ui.py4
-rw-r--r--gn_auth/auth/authorisation/users/admin/views.py41
-rw-r--r--gn_auth/auth/authorisation/users/collections/models.py4
-rw-r--r--gn_auth/auth/authorisation/users/collections/views.py1
-rw-r--r--gn_auth/auth/authorisation/users/masquerade/models.py43
-rw-r--r--gn_auth/auth/authorisation/users/masquerade/views.py13
-rw-r--r--gn_auth/auth/authorisation/users/views.py35
7 files changed, 102 insertions, 39 deletions
diff --git a/gn_auth/auth/authorisation/users/admin/ui.py b/gn_auth/auth/authorisation/users/admin/ui.py
index 64e79a0..43ca0a2 100644
--- a/gn_auth/auth/authorisation/users/admin/ui.py
+++ b/gn_auth/auth/authorisation/users/admin/ui.py
@@ -1,6 +1,6 @@
"""UI utilities for the auth system."""
from functools import wraps
-from flask import flash, url_for, redirect
+from flask import flash, request, url_for, redirect
from gn_auth.session import logged_in, session_user, clear_session_info
from gn_auth.auth.authorisation.resources.system.models import (
@@ -24,5 +24,5 @@ def is_admin(func):
flash("Expected a system administrator.", "alert-danger")
flash("You have been logged out of the system.", "alert-info")
clear_session_info()
- return redirect(url_for("oauth2.admin.login"))
+ return redirect(url_for("oauth2.admin.login", **dict(request.args)))
return __admin__
diff --git a/gn_auth/auth/authorisation/users/admin/views.py b/gn_auth/auth/authorisation/users/admin/views.py
index 85aeb50..9bc1c36 100644
--- a/gn_auth/auth/authorisation/users/admin/views.py
+++ b/gn_auth/auth/authorisation/users/admin/views.py
@@ -30,6 +30,7 @@ from ....authentication.oauth2.models.oauth2client import (
save_client,
OAuth2Client,
oauth2_clients,
+ update_client_attribute,
client as oauth2_client,
delete_client as _delete_client)
from ....authentication.users import (
@@ -97,7 +98,7 @@ def login():
expires=(
datetime.now(tz=timezone.utc) + timedelta(minutes=int(
app.config.get("SESSION_EXPIRY_MINUTES", 10)))))
- return redirect(url_for(next_uri))
+ return redirect(url_for(next_uri, **dict(request.args)))
raise NotFoundError(error_message)
except NotFoundError as _nfe:
flash(error_message, "alert-danger")
@@ -196,7 +197,7 @@ def register_client():
if request.method == "GET":
return render_template(
"admin/register-client.html",
- scope=app.config["OAUTH2_SCOPE"],
+ scope=app.config["OAUTH2_SCOPES_SUPPORTED"],
users=with_db_connection(__list_users__),
granttypes=_FORM_GRANT_TYPES_,
current_user=session.session_user())
@@ -261,7 +262,7 @@ def view_client(client_id: uuid.UUID):
return render_template(
"admin/view-oauth2-client.html",
client=with_db_connection(partial(oauth2_client, client_id=client_id)),
- scope=app.config["OAUTH2_SCOPE"],
+ scope=app.config["OAUTH2_SCOPES_SUPPORTED"],
granttypes=_FORM_GRANT_TYPES_)
@@ -321,3 +322,37 @@ def delete_client():
"successfully."),
"alert-success")
return redirect(url_for("oauth2.admin.list_clients"))
+
+
+@admin.route("/clients/<uuid:client_id>/change-secret", methods=["GET", "POST"])
+@is_admin
+def change_client_secret(client_id: uuid.UUID):
+ """Enable changing of a client's secret."""
+ def __no_client__():
+ # Calling the function causes the flash to be evaluated
+ # flash("No such client was found!", "alert-danger")
+ return redirect(url_for("oauth2.admin.list_clients"))
+
+ with db.connection(app.config["AUTH_DB"]) as conn:
+ if request.method == "GET":
+ return oauth2_client(
+ conn, client_id=client_id
+ ).maybe(__no_client__(), lambda _client: render_template(
+ "admin/confirm-change-client-secret.html",
+ client=_client
+ ))
+
+ _raw = random_string()
+ return oauth2_client(
+ conn, client_id=client_id
+ ).then(
+ lambda _client: save_client(
+ conn,
+ update_client_attribute(
+ _client, "client_secret", hash_password(_raw)))
+ ).then(
+ lambda _client: render_template(
+ "admin/registered-client.html",
+ client=_client,
+ client_secret=_raw)
+ ).maybe(__no_client__(), lambda resp: resp)
diff --git a/gn_auth/auth/authorisation/users/collections/models.py b/gn_auth/auth/authorisation/users/collections/models.py
index b4a24f3..f0a7fa2 100644
--- a/gn_auth/auth/authorisation/users/collections/models.py
+++ b/gn_auth/auth/authorisation/users/collections/models.py
@@ -205,8 +205,10 @@ def add_traits(rconn: Redis,
mod_col = tuple(coll for coll in ucolls if coll["id"] == collection_id)
__raise_if_not_single_collection__(user, collection_id, mod_col)
new_members = tuple(set(tuple(mod_col[0]["members"]) + traits))
+ now = datetime.utcnow()
new_coll = {
**mod_col[0],
+ "changed": now,
"members": new_members,
"num_members": len(new_members)
}
@@ -233,8 +235,10 @@ def remove_traits(rconn: Redis,
__raise_if_not_single_collection__(user, collection_id, mod_col)
new_members = tuple(
trait for trait in mod_col[0]["members"] if trait not in traits)
+ now = datetime.utcnow()
new_coll = {
**mod_col[0],
+ "changed": now,
"members": new_members,
"num_members": len(new_members)
}
diff --git a/gn_auth/auth/authorisation/users/collections/views.py b/gn_auth/auth/authorisation/users/collections/views.py
index eeae91d..f619c3d 100644
--- a/gn_auth/auth/authorisation/users/collections/views.py
+++ b/gn_auth/auth/authorisation/users/collections/views.py
@@ -113,6 +113,7 @@ def import_anonymous() -> Response:
anon_id = UUID(request.json.get("anon_id"))#type: ignore[union-attr]
anon_colls = user_collections(redisconn, User(
anon_id, "anon@ymous.user", "Anonymous User"))
+ anon_colls = tuple(coll for coll in anon_colls if coll['num_members'] > 0)
save_collections(
redisconn,
token.user,
diff --git a/gn_auth/auth/authorisation/users/masquerade/models.py b/gn_auth/auth/authorisation/users/masquerade/models.py
index 8ac1a68..a155899 100644
--- a/gn_auth/auth/authorisation/users/masquerade/models.py
+++ b/gn_auth/auth/authorisation/users/masquerade/models.py
@@ -1,5 +1,4 @@
"""Functions for handling masquerade."""
-import uuid
from functools import wraps
from datetime import datetime
from authlib.jose import jwt
@@ -10,12 +9,16 @@ from flask import current_app as app
from gn_auth.auth.errors import ForbiddenAccess
from gn_auth.auth.jwks import newest_jwk_with_rotation, jwks_directory
+from gn_auth.auth.authentication.oauth2.grants.refresh_token_grant import (
+ RefreshTokenGrant)
+from gn_auth.auth.authentication.oauth2.models.jwtrefreshtoken import (
+ JWTRefreshToken,
+ save_refresh_token)
from ...roles.models import user_roles
from ....db import sqlite3 as db
from ....authentication.users import User
-from ....authentication.oauth2.models.oauth2token import (
- OAuth2Token, save_token)
+from ....authentication.oauth2.models.oauth2token import OAuth2Token
__FIVE_HOURS__ = (60 * 60 * 5)
@@ -53,28 +56,30 @@ def masquerade_as(
original_token: OAuth2Token,
masqueradee: User) -> OAuth2Token:
"""Get a token that enables `masquerader` to act as `masqueradee`."""
- token_details = app.config["OAUTH2_SERVER"].generate_token(
+ scope = original_token.get_scope().replace(
+ # Do not allow more than one level of masquerading
+ "masquerade", "").strip()
+ new_token = app.config["OAUTH2_SERVER"].generate_token(
client=original_token.client,
- grant_type="authorization_code",
+ grant_type="urn:ietf:params:oauth:grant-type:jwt-bearer",
user=masqueradee,
- expires_in=__FIVE_HOURS__,
- include_refresh_token=True)
-
+ expires_in=original_token.get_expires_in(),
+ include_refresh_token=True,
+ scope=scope)
_jwt = jwt.decode(
- original_token.access_token,
+ new_token["access_token"],
newest_jwk_with_rotation(
jwks_directory(app),
int(app.config["JWKS_ROTATION_AGE_DAYS"])))
- new_token = OAuth2Token(
- token_id=uuid.UUID(_jwt["jti"]),
+ save_refresh_token(conn, JWTRefreshToken(
+ token=new_token["refresh_token"],
client=original_token.client,
- token_type=token_details["token_type"],
- access_token=token_details["access_token"],
- refresh_token=token_details.get("refresh_token"),
- scope=original_token.scope,
+ user=masqueradee,
+ issued_with=_jwt["jti"],
+ issued_at=datetime.fromtimestamp(_jwt["iat"]),
+ expires=datetime.fromtimestamp(
+ int(_jwt["iat"]) + RefreshTokenGrant.DEFAULT_EXPIRES_IN),
+ scope=scope,
revoked=False,
- issued_at=datetime.now(),
- expires_in=token_details["expires_in"],
- user=masqueradee)
- save_token(conn, new_token)
+ parent_of=None))
return new_token
diff --git a/gn_auth/auth/authorisation/users/masquerade/views.py b/gn_auth/auth/authorisation/users/masquerade/views.py
index 68f19ee..8b897f2 100644
--- a/gn_auth/auth/authorisation/users/masquerade/views.py
+++ b/gn_auth/auth/authorisation/users/masquerade/views.py
@@ -28,22 +28,17 @@ def masquerade() -> Response:
masq_user = with_db_connection(partial(
user_by_id, user_id=masqueradee_id))
+
def __masq__(conn):
new_token = masquerade_as(conn, original_token=token, masqueradee=masq_user)
return new_token
- def __dump_token__(tok):
- return {
- key: value for key, value in asdict(tok).items()
- if key in ("access_token", "refresh_token", "expires_in",
- "token_type")
- }
+
return jsonify({
"original": {
- "user": asdict(token.user),
- "token": __dump_token__(token)
+ "user": asdict(token.user)
},
"masquerade_as": {
"user": asdict(masq_user),
- "token": __dump_token__(with_db_connection(__masq__))
+ "token": with_db_connection(__masq__)
}
})
diff --git a/gn_auth/auth/authorisation/users/views.py b/gn_auth/auth/authorisation/users/views.py
index 4cd498c..7adcd06 100644
--- a/gn_auth/auth/authorisation/users/views.py
+++ b/gn_auth/auth/authorisation/users/views.py
@@ -152,8 +152,8 @@ def send_verification_email(
timedelta(
minutes=expiration_minutes)).timestamp())
})
- send_message(smtp_user=current_app.config["SMTP_USER"],
- smtp_passwd=current_app.config["SMTP_PASSWORD"],
+ send_message(smtp_user=current_app.config.get("SMTP_USER", ""),
+ smtp_passwd=current_app.config.get("SMTP_PASSWORD", ""),
message=build_email_message(
from_address=current_app.config["EMAIL_ADDRESS"],
to_addresses=(user_address(user),),
@@ -435,8 +435,7 @@ def forgot_password():
flash("You MUST provide an email.", "alert-danger")
return redirect(url_for("oauth2.users.forgot_password"))
- with (db.connection(current_app.config["AUTH_DB"]) as conn,
- db.cursor(conn) as cursor):
+ with db.connection(current_app.config["AUTH_DB"]) as conn:
user = user_by_email(conn, form["email"])
if not bool(user):
flash("We could not find an account with that email.",
@@ -467,8 +466,8 @@ def change_password(forgot_password_token):
"SELECT fpt.*, u.email FROM forgot_password_tokens AS fpt "
"INNER JOIN users AS u ON fpt.user_id=u.user_id WHERE token=?",
(forgot_password_token,))
+ token = cursor.fetchone()
if request.method == "GET":
- token = cursor.fetchone()
if bool(token):
return render_template(
"users/change-password.html",
@@ -480,4 +479,28 @@ def change_password(forgot_password_token):
flash("Invalid Token: We cannot change your password!",
"alert-danger")
return login_page
- return "Do actual password change..."
+
+ password = request.form["password"]
+ confirm_password = request.form["confirm-password"]
+ change_password_page = redirect(url_for(
+ "oauth2.users.change_password",
+ client_id=request.args["client_id"],
+ redirect_uri=request.args["redirect_uri"],
+ response_type=request.args["response_type"],
+ forgot_password_token=forgot_password_token))
+ if bool(password) and bool(confirm_password):
+ if password == confirm_password:
+ _user, _hashed_password = set_user_password(
+ cursor, user_by_email(conn, token["email"]), password)
+ cursor.execute(
+ "DELETE FROM forgot_password_tokens WHERE token=?",
+ (forgot_password_token,))
+ flash("Password changed successfully!", "alert-success")
+ return login_page
+
+ flash("Passwords do not match!", "alert-danger")
+ return change_password_page
+
+ flash("Both the password and its confirmation MUST be provided!",
+ "alert-danger")
+ return change_password_page