move everything to backend folder for migration to dashboard repository
This commit is contained in:
parent
af6b006409
commit
92ec7c653d
89 changed files with 0 additions and 0 deletions
2
backend/areas/users/__init__.py
Normal file
2
backend/areas/users/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from .users import *
|
||||
from .user_service import *
|
||||
195
backend/areas/users/user_service.py
Normal file
195
backend/areas/users/user_service.py
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
import ory_kratos_client
|
||||
from ory_kratos_client.model.submit_self_service_recovery_flow_body \
|
||||
import SubmitSelfServiceRecoveryFlowBody
|
||||
from ory_kratos_client.api import v0alpha2_api as kratos_api
|
||||
from config import KRATOS_ADMIN_URL
|
||||
|
||||
from database import db
|
||||
from areas.apps import App, AppRole, AppsService
|
||||
from areas.roles import Role, RoleService
|
||||
from helpers import KratosApi
|
||||
|
||||
from flask import current_app
|
||||
|
||||
from helpers.error_handler import KratosError
|
||||
|
||||
kratos_admin_api_configuration = \
|
||||
ory_kratos_client.Configuration(host=KRATOS_ADMIN_URL, discard_unknown_keys=True)
|
||||
KRATOS_ADMIN = \
|
||||
kratos_api.V0alpha2Api(ory_kratos_client.ApiClient(kratos_admin_api_configuration))
|
||||
|
||||
class UserService:
|
||||
@staticmethod
|
||||
def get_users():
|
||||
res = KratosApi.get("/admin/identities").json()
|
||||
userList = []
|
||||
for r in res:
|
||||
userList.append(UserService.__insertAppRoleToUser(r["id"], r))
|
||||
|
||||
return userList
|
||||
|
||||
@staticmethod
|
||||
def get_user(id):
|
||||
res = KratosApi.get("/admin/identities/{}".format(id)).json()
|
||||
return UserService.__insertAppRoleToUser(id, res)
|
||||
|
||||
@staticmethod
|
||||
def post_user(data):
|
||||
kratos_data = {
|
||||
"schema_id": "default",
|
||||
"traits": {
|
||||
"name": data["name"],
|
||||
"email": data["email"],
|
||||
},
|
||||
}
|
||||
res = KratosApi.post("/admin/identities", kratos_data).json()
|
||||
|
||||
if data["app_roles"]:
|
||||
app_roles = data["app_roles"]
|
||||
for ar in app_roles:
|
||||
app = App.query.filter_by(slug=ar["name"]).first()
|
||||
app_role = AppRole(
|
||||
user_id=res["id"],
|
||||
role_id=ar["role_id"] if "role_id" in ar else Role.NO_ACCESS_ROLE_ID,
|
||||
app_id=app.id,
|
||||
)
|
||||
|
||||
db.session.add(app_role)
|
||||
db.session.commit()
|
||||
else:
|
||||
all_apps = AppsService.get_all_apps()
|
||||
for app in all_apps:
|
||||
app_role = AppRole(
|
||||
user_id=res["id"],
|
||||
role_id=Role.NO_ACCESS_ROLE_ID,
|
||||
app_id=app.id,
|
||||
)
|
||||
|
||||
db.session.add(app_role)
|
||||
db.session.commit()
|
||||
|
||||
UserService.__start_recovery_flow(data["email"])
|
||||
|
||||
return UserService.get_user(res["id"])
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __start_recovery_flow(email):
|
||||
"""
|
||||
Start a Kratos recovery flow for the user's email address.
|
||||
|
||||
This sends out an email to the user that explains to them how they can
|
||||
set their password. Make sure the user exists inside Kratos before you
|
||||
use this function.
|
||||
|
||||
:param email: Email to send recovery link to
|
||||
:type email: str
|
||||
"""
|
||||
api_response = KRATOS_ADMIN.initialize_self_service_recovery_flow_without_browser()
|
||||
flow = api_response['id']
|
||||
# Submit the recovery flow to send an email to the new user.
|
||||
submit_self_service_recovery_flow_body = \
|
||||
SubmitSelfServiceRecoveryFlowBody(method="link", email=email)
|
||||
api_response = KRATOS_ADMIN.submit_self_service_recovery_flow(flow,
|
||||
submit_self_service_recovery_flow_body=
|
||||
submit_self_service_recovery_flow_body)
|
||||
|
||||
@staticmethod
|
||||
def put_user(id, user_editing_id, data):
|
||||
kratos_data = {
|
||||
"schema_id": "default",
|
||||
"traits": {"email": data["email"], "name": data["name"]},
|
||||
}
|
||||
KratosApi.put("/admin/identities/{}".format(id), kratos_data)
|
||||
|
||||
is_admin = RoleService.is_user_admin(user_editing_id)
|
||||
|
||||
if is_admin and data["app_roles"]:
|
||||
app_roles = data["app_roles"]
|
||||
for ar in app_roles:
|
||||
app = App.query.filter_by(slug=ar["name"]).first()
|
||||
app_role = AppRole.query.filter_by(
|
||||
user_id=id, app_id=app.id).first()
|
||||
|
||||
if app_role:
|
||||
app_role.role_id = ar["role_id"] if "role_id" in ar else None
|
||||
db.session.commit()
|
||||
else:
|
||||
appRole = AppRole(
|
||||
user_id=id,
|
||||
role_id=ar["role_id"] if "role_id" in ar else None,
|
||||
app_id=app.id,
|
||||
)
|
||||
db.session.add(appRole)
|
||||
db.session.commit()
|
||||
|
||||
return UserService.get_user(id)
|
||||
|
||||
@staticmethod
|
||||
def delete_user(id):
|
||||
app_role = AppRole.query.filter_by(user_id=id).all()
|
||||
for ar in app_role:
|
||||
db.session.delete(ar)
|
||||
db.session.commit()
|
||||
|
||||
@staticmethod
|
||||
def post_multiple_users(data):
|
||||
# check if data is array
|
||||
# for every item in array call Kratos
|
||||
created_users = []
|
||||
existing_users = []
|
||||
creation_failed_users = []
|
||||
|
||||
for user_data in data['users']:
|
||||
user_email = user_data["email"]
|
||||
if not user_email:
|
||||
return
|
||||
try:
|
||||
UserService.post_user(user_data)
|
||||
current_app.logger.info(f"Batch create user: {user_email}")
|
||||
created_users.append(user_email)
|
||||
except KratosError as err:
|
||||
status_code = err.args[1]
|
||||
if status_code == 409:
|
||||
existing_users.append(user_email)
|
||||
elif status_code == 400:
|
||||
creation_failed_users.append(user_email)
|
||||
current_app.logger.error(
|
||||
f"Exception calling Kratos: {err} on creating user: {user_email} {status_code}")
|
||||
except Exception as error:
|
||||
current_app.logger.error(
|
||||
f"Exception: {error} on creating user: {user_email}")
|
||||
creation_failed_users.append(user_email)
|
||||
|
||||
success_response = {}
|
||||
existing_response = {}
|
||||
failed_response = {}
|
||||
if created_users:
|
||||
success_response = {"users": created_users,
|
||||
"message": f"{len(created_users)} users created"}
|
||||
if existing_users:
|
||||
existing_response = {
|
||||
"users": existing_users, "message": f"{len(existing_users)} users already exist: {', '.join(existing_users)}"}
|
||||
if creation_failed_users:
|
||||
failed_response = {"users": creation_failed_users,
|
||||
"message": f"{len(creation_failed_users)} users failed to create: {', '.join(creation_failed_users)}"}
|
||||
|
||||
return {"success": success_response, "existing": existing_response, "failed": failed_response}
|
||||
|
||||
@staticmethod
|
||||
def __insertAppRoleToUser(userId, userRes):
|
||||
apps = App.query.all()
|
||||
app_roles = []
|
||||
for app in apps:
|
||||
tmp_app_role = AppRole.query.filter_by(
|
||||
user_id=userId, app_id=app.id
|
||||
).first()
|
||||
app_roles.append(
|
||||
{
|
||||
"name": app.slug,
|
||||
"role_id": tmp_app_role.role_id if tmp_app_role else None,
|
||||
}
|
||||
)
|
||||
|
||||
userRes["traits"]["app_roles"] = app_roles
|
||||
return userRes
|
||||
101
backend/areas/users/users.py
Normal file
101
backend/areas/users/users.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
from flask import jsonify, request
|
||||
from flask_jwt_extended import get_jwt, jwt_required
|
||||
from flask_cors import cross_origin
|
||||
from flask_expects_json import expects_json
|
||||
|
||||
from areas import api_v1
|
||||
from helpers import KratosApi
|
||||
from helpers.auth_guard import admin_required
|
||||
|
||||
from .validation import schema, schema_multiple
|
||||
from .user_service import UserService
|
||||
|
||||
|
||||
@api_v1.route("/users", methods=["GET"])
|
||||
@jwt_required()
|
||||
@cross_origin()
|
||||
@admin_required()
|
||||
def get_users():
|
||||
res = UserService.get_users()
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@api_v1.route("/users/<string:id>", methods=["GET"])
|
||||
@jwt_required()
|
||||
@cross_origin()
|
||||
@admin_required()
|
||||
def get_user(id):
|
||||
res = UserService.get_user(id)
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@api_v1.route("/users", methods=["POST"])
|
||||
@jwt_required()
|
||||
@cross_origin()
|
||||
@expects_json(schema)
|
||||
@admin_required()
|
||||
def post_user():
|
||||
data = request.get_json()
|
||||
res = UserService.post_user(data)
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@api_v1.route("/users/<string:id>", methods=["PUT"])
|
||||
@jwt_required()
|
||||
@cross_origin()
|
||||
@expects_json(schema)
|
||||
@admin_required()
|
||||
def put_user(id):
|
||||
data = request.get_json()
|
||||
user_id = __get_user_id_from_jwt()
|
||||
res = UserService.put_user(id, user_id, data)
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@api_v1.route("/users/<string:id>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
@cross_origin()
|
||||
@admin_required()
|
||||
def delete_user(id):
|
||||
res = KratosApi.delete("/identities/{}".format(id))
|
||||
if res.status_code == 204:
|
||||
UserService.delete_user(id)
|
||||
return jsonify(), res.status_code
|
||||
return jsonify(res.json()), res.status_code
|
||||
|
||||
|
||||
@api_v1.route("/users-batch", methods=["POST"])
|
||||
@jwt_required()
|
||||
@cross_origin()
|
||||
@expects_json(schema_multiple)
|
||||
@admin_required()
|
||||
def post_multiple_users():
|
||||
"""Expects an array of user JSON schema in request body."""
|
||||
data = request.get_json()
|
||||
res = UserService.post_multiple_users(data)
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@api_v1.route("/me", methods=["GET"])
|
||||
@jwt_required()
|
||||
@cross_origin()
|
||||
def get_personal_info():
|
||||
user_id = __get_user_id_from_jwt()
|
||||
res = UserService.get_user(user_id)
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
@api_v1.route("/me", methods=["PUT"])
|
||||
@jwt_required()
|
||||
@cross_origin()
|
||||
@expects_json(schema)
|
||||
def update_personal_info():
|
||||
data = request.get_json()
|
||||
user_id = __get_user_id_from_jwt()
|
||||
res = UserService.put_user(user_id, user_id, data)
|
||||
return jsonify(res)
|
||||
|
||||
|
||||
def __get_user_id_from_jwt():
|
||||
claims = get_jwt()
|
||||
return claims["user_id"]
|
||||
42
backend/areas/users/validation.py
Normal file
42
backend/areas/users/validation.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import re
|
||||
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Email of the user",
|
||||
"pattern": r"(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])",
|
||||
"minLength": 1,
|
||||
},
|
||||
"app_roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name of the app",
|
||||
"minLenght": 1,
|
||||
},
|
||||
"role_id": {
|
||||
"type": ["integer", "null"],
|
||||
"description": "Role of the user",
|
||||
"minimum": 1,
|
||||
},
|
||||
},
|
||||
"required": ["name", "role_id"],
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["email", "app_roles"],
|
||||
}
|
||||
|
||||
schema_multiple = {
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": schema
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in a new issue