Merge branch 'api-jwt' into 'master'
Api jwt See merge request isard/isard-sso!78
commit
0e550746d6
|
@ -3,7 +3,6 @@ Flask-Login==0.5.0
|
||||||
eventlet==0.33.0
|
eventlet==0.33.0
|
||||||
Flask-SocketIO==5.1.0
|
Flask-SocketIO==5.1.0
|
||||||
bcrypt==3.2.0
|
bcrypt==3.2.0
|
||||||
|
|
||||||
diceware==0.9.6
|
diceware==0.9.6
|
||||||
mysql-connector-python==8.0.25
|
mysql-connector-python==8.0.25
|
||||||
psycopg2==2.8.6
|
psycopg2==2.8.6
|
||||||
|
@ -12,5 +11,6 @@ minio==7.0.3
|
||||||
urllib3==1.26.6
|
urllib3==1.26.6
|
||||||
schema==0.7.5
|
schema==0.7.5
|
||||||
Werkzeug~=2.0.0
|
Werkzeug~=2.0.0
|
||||||
# Unused yet
|
python-jose==3.3.0
|
||||||
#flask-oidc==1.4.0
|
Cerberus==1.3.4
|
||||||
|
PyYAML==6.0
|
||||||
|
|
|
@ -108,4 +108,4 @@ def send_custom(path):
|
||||||
"""
|
"""
|
||||||
Import all views
|
Import all views
|
||||||
"""
|
"""
|
||||||
from .views import ApiViews, InternalViews, LoginViews, WebViews
|
from .views import ApiViews, AppViews, LoginViews, WebViews, WpViews
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Copyright 2017 the Isard-vdi project authors:
|
||||||
|
# Josep Maria Viñolas Auquer
|
||||||
|
# Alberto Larraz Dalmases
|
||||||
|
# License: AGPLv3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging as log
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
from flask import request
|
||||||
|
from jose import jwt
|
||||||
|
|
||||||
|
from admin import app
|
||||||
|
|
||||||
|
from ..lib.api_exceptions import Error
|
||||||
|
|
||||||
|
|
||||||
|
def get_header_jwt_payload():
|
||||||
|
return get_token_payload(get_token_auth_header())
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_header(header):
|
||||||
|
"""Obtains the Access Token from the a Header"""
|
||||||
|
auth = request.headers.get(header, None)
|
||||||
|
if not auth:
|
||||||
|
raise Error(
|
||||||
|
"unauthorized",
|
||||||
|
"Authorization header is expected",
|
||||||
|
traceback.format_stack(),
|
||||||
|
)
|
||||||
|
|
||||||
|
parts = auth.split()
|
||||||
|
if parts[0].lower() != "bearer":
|
||||||
|
raise Error(
|
||||||
|
"unauthorized",
|
||||||
|
"Authorization header must start with Bearer",
|
||||||
|
traceback.format_stack(),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif len(parts) == 1:
|
||||||
|
raise Error("bad_request", "Token not found")
|
||||||
|
elif len(parts) > 2:
|
||||||
|
raise Error(
|
||||||
|
"unauthorized",
|
||||||
|
"Authorization header must be Bearer token",
|
||||||
|
traceback.format_stack(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return parts[1] # Token
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_auth_header():
|
||||||
|
return get_token_header("Authorization")
|
||||||
|
|
||||||
|
|
||||||
|
def get_token_payload(token):
|
||||||
|
# log.warning("The received token in get_token_payload is: " + str(token))
|
||||||
|
try:
|
||||||
|
claims = jwt.get_unverified_claims(token)
|
||||||
|
secret = app.config["API_SECRET"]
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.warning(
|
||||||
|
"JWT token with invalid parameters. Can not parse it.: " + str(token)
|
||||||
|
)
|
||||||
|
raise Error(
|
||||||
|
"unauthorized",
|
||||||
|
"Unable to parse authentication parameters token.",
|
||||||
|
traceback.format_stack(),
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(
|
||||||
|
token,
|
||||||
|
secret,
|
||||||
|
algorithms=["HS256"],
|
||||||
|
options=dict(verify_aud=False, verify_sub=False, verify_exp=True),
|
||||||
|
)
|
||||||
|
except jwt.ExpiredSignatureError:
|
||||||
|
log.warning("Token expired")
|
||||||
|
raise Error("unauthorized", "Token is expired", traceback.format_stack())
|
||||||
|
|
||||||
|
except jwt.JWTClaimsError:
|
||||||
|
raise Error(
|
||||||
|
"unauthorized",
|
||||||
|
"Incorrect claims, please check the audience and issuer",
|
||||||
|
traceback.format_stack(),
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
raise Error(
|
||||||
|
"unauthorized",
|
||||||
|
"Unable to parse authentication token.",
|
||||||
|
traceback.format_stack(),
|
||||||
|
)
|
||||||
|
if payload.get("data", False):
|
||||||
|
return payload["data"]
|
||||||
|
return payload
|
|
@ -30,7 +30,8 @@ options.num = 3
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
from .events import Events
|
from .api_exceptions import Error
|
||||||
|
from .events import Events, sio_event_send
|
||||||
from .exceptions import UserExists, UserNotFound
|
from .exceptions import UserExists, UserNotFound
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
count_repeated,
|
count_repeated,
|
||||||
|
@ -161,7 +162,7 @@ class Admin:
|
||||||
ddmail,
|
ddmail,
|
||||||
ddpassword,
|
ddpassword,
|
||||||
group="admin",
|
group="admin",
|
||||||
temporary=False,
|
password_temporary=False,
|
||||||
)
|
)
|
||||||
self.keycloak.assign_realm_roles(uid, "admin")
|
self.keycloak.assign_realm_roles(uid, "admin")
|
||||||
log.warning("KEYCLOAK: OK")
|
log.warning("KEYCLOAK: OK")
|
||||||
|
@ -395,6 +396,7 @@ class Admin:
|
||||||
# return users_list
|
# return users_list
|
||||||
|
|
||||||
def get_mix_users(self):
|
def get_mix_users(self):
|
||||||
|
sio_event_send("get_users", {"you_win": "you got the users!"})
|
||||||
return self.internal["users"]
|
return self.internal["users"]
|
||||||
|
|
||||||
def _get_mix_users(self):
|
def _get_mix_users(self):
|
||||||
|
@ -466,6 +468,10 @@ class Admin:
|
||||||
def _get_roles(self):
|
def _get_roles(self):
|
||||||
return filter_roles_listofdicts(self.keycloak.get_roles())
|
return filter_roles_listofdicts(self.keycloak.get_roles())
|
||||||
|
|
||||||
|
def get_group_by_name(self, group_name):
|
||||||
|
group = [g for g in self.internal["groups"] if g["name"] == group_name]
|
||||||
|
return group[0] if len(group) else False
|
||||||
|
|
||||||
def get_keycloak_groups(self):
|
def get_keycloak_groups(self):
|
||||||
log.warning("Loading keycloak groups...")
|
log.warning("Loading keycloak groups...")
|
||||||
return self.keycloak.get_groups()
|
return self.keycloak.get_groups()
|
||||||
|
@ -627,7 +633,7 @@ class Admin:
|
||||||
"gids": pathslist,
|
"gids": pathslist,
|
||||||
"quota": u["quota"],
|
"quota": u["quota"],
|
||||||
"roles": [u["role"].strip()],
|
"roles": [u["role"].strip()],
|
||||||
"temporary": True
|
"password_temporary": True
|
||||||
if u["password_temporal"].lower() == "yes"
|
if u["password_temporal"].lower() == "yes"
|
||||||
else False,
|
else False,
|
||||||
"password": self.get_dice_pwd()
|
"password": self.get_dice_pwd()
|
||||||
|
@ -798,7 +804,7 @@ class Admin:
|
||||||
u["last"],
|
u["last"],
|
||||||
u["email"],
|
u["email"],
|
||||||
u["password"],
|
u["password"],
|
||||||
temporary=u["temporary"],
|
password_temporary=u["password_temporary"],
|
||||||
)
|
)
|
||||||
self.av.add_user_default_avatar(uid, u["roles"][0])
|
self.av.add_user_default_avatar(uid, u["roles"][0])
|
||||||
# Add user to role and group rolename
|
# Add user to role and group rolename
|
||||||
|
@ -1291,8 +1297,8 @@ class Admin:
|
||||||
externaluser["gids"].append(data["action"])
|
externaluser["gids"].append(data["action"])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def user_update_password(self, userid, password, temporary):
|
def user_update_password(self, userid, password, password_temporary):
|
||||||
return self.keycloak.update_user_pwd(userid, password, temporary)
|
return self.keycloak.update_user_pwd(userid, password, password_temporary)
|
||||||
|
|
||||||
def update_users_from_keycloak(self):
|
def update_users_from_keycloak(self):
|
||||||
kgroups = self.keycloak.get_groups()
|
kgroups = self.keycloak.get_groups()
|
||||||
|
@ -1669,6 +1675,7 @@ class Admin:
|
||||||
ev.update_text("Syncing data from applications...")
|
ev.update_text("Syncing data from applications...")
|
||||||
self.resync_data()
|
self.resync_data()
|
||||||
ev.update_text("User deleted")
|
ev.update_text("User deleted")
|
||||||
|
sio_event_send("delete_user", {"userid": userid})
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_user(self, userid):
|
def get_user(self, userid):
|
||||||
|
@ -1695,6 +1702,22 @@ class Admin:
|
||||||
pathpart = pathpart + "." + part
|
pathpart = pathpart + "." + part
|
||||||
pathslist.append(pathpart)
|
pathslist.append(pathpart)
|
||||||
|
|
||||||
|
for path in pathslist:
|
||||||
|
path = "/" + path.replace(".", "/")
|
||||||
|
log.warning(
|
||||||
|
" KEYCLOAK USERS: Assign user " + u["username"] + " to group " + path
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
gid = self.keycloak.get_group_by_path(path=path)["id"]
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
# gid = self.keycloak.add_group_tree(path)
|
||||||
|
# log.warning("THE PATH "+str(path)+" HAS GID "+str(gid))
|
||||||
|
# self.moodle.add_system_cohort(path)
|
||||||
|
# self.nextcloud.add_group(path)
|
||||||
|
# self.resync_data()
|
||||||
|
# gid = self.keycloak.get_group_by_path(path=path)["id"]
|
||||||
|
|
||||||
### KEYCLOAK
|
### KEYCLOAK
|
||||||
#######################
|
#######################
|
||||||
ev = Events("Add user", u["username"], total=5)
|
ev = Events("Add user", u["username"], total=5)
|
||||||
|
@ -1706,18 +1729,14 @@ class Admin:
|
||||||
u["email"],
|
u["email"],
|
||||||
u["password"],
|
u["password"],
|
||||||
enabled=u["enabled"],
|
enabled=u["enabled"],
|
||||||
|
password_temporary=u.get("password_temporary", True),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.av.add_user_default_avatar(uid, u["role"])
|
self.av.add_user_default_avatar(uid, u["role"])
|
||||||
|
|
||||||
# Add user to role and group rolename
|
# Add user to role and group rolename
|
||||||
log.warning(
|
log.warning(
|
||||||
" KEYCLOAK USERS: Assign user "
|
" KEYCLOAK USERS: Assign user " + u["username"] + " to role " + u["role"]
|
||||||
+ u["username"]
|
|
||||||
+ " with initial pwd "
|
|
||||||
+ u["password"]
|
|
||||||
+ " to role "
|
|
||||||
+ u["role"]
|
|
||||||
)
|
)
|
||||||
self.keycloak.assign_realm_roles(uid, u["role"])
|
self.keycloak.assign_realm_roles(uid, u["role"])
|
||||||
gid = self.keycloak.get_group_by_path(path="/" + u["role"])["id"]
|
gid = self.keycloak.get_group_by_path(path="/" + u["role"])["id"]
|
||||||
|
@ -1726,9 +1745,6 @@ class Admin:
|
||||||
# Add user to groups
|
# Add user to groups
|
||||||
for path in pathslist:
|
for path in pathslist:
|
||||||
path = "/" + path.replace(".", "/")
|
path = "/" + path.replace(".", "/")
|
||||||
log.warning(
|
|
||||||
" KEYCLOAK USERS: Assign user " + u["username"] + " to group " + path
|
|
||||||
)
|
|
||||||
gid = self.keycloak.get_group_by_path(path=path)["id"]
|
gid = self.keycloak.get_group_by_path(path=path)["id"]
|
||||||
self.keycloak.group_user_add(uid, gid)
|
self.keycloak.group_user_add(uid, gid)
|
||||||
ev.increment({"name": "Added to system groups", "data": []})
|
ev.increment({"name": "Added to system groups", "data": []})
|
||||||
|
@ -1812,6 +1828,8 @@ class Admin:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
|
|
||||||
self.resync_data()
|
self.resync_data()
|
||||||
|
sio_event_send("new_user", u)
|
||||||
|
return uid
|
||||||
|
|
||||||
def add_group(self, g):
|
def add_group(self, g):
|
||||||
# TODO: Check if exists
|
# TODO: Check if exists
|
||||||
|
@ -1830,6 +1848,7 @@ class Admin:
|
||||||
self.moodle.add_system_cohort(new_path, description=g["description"])
|
self.moodle.add_system_cohort(new_path, description=g["description"])
|
||||||
self.nextcloud.add_group(new_path)
|
self.nextcloud.add_group(new_path)
|
||||||
self.resync_data()
|
self.resync_data()
|
||||||
|
return new_path
|
||||||
|
|
||||||
def delete_group_by_id(self, group_id):
|
def delete_group_by_id(self, group_id):
|
||||||
ev = Events("Deleting group", "Deleting from keycloak")
|
ev = Events("Deleting group", "Deleting from keycloak")
|
||||||
|
@ -1843,6 +1862,7 @@ class Admin:
|
||||||
+ str(group_id)
|
+ str(group_id)
|
||||||
+ " as it does not exist!"
|
+ " as it does not exist!"
|
||||||
)
|
)
|
||||||
|
raise Error("not_found", "Group " + group_id + " not found.")
|
||||||
|
|
||||||
# {'id': '966ad67c-499a-4f56-bd1d-283691cde0e7', 'name': 'asdgfewfwe', 'path': '/asdgfewfwe', 'attributes': {}, 'realmRoles': [], 'clientRoles': {}, 'subGroups': [], 'access': {'view': True, 'manage': True, 'manageMembership': True}}
|
# {'id': '966ad67c-499a-4f56-bd1d-283691cde0e7', 'name': 'asdgfewfwe', 'path': '/asdgfewfwe', 'attributes': {}, 'realmRoles': [], 'clientRoles': {}, 'subGroups': [], 'access': {'view': True, 'manage': True, 'manageMembership': True}}
|
||||||
|
|
||||||
|
@ -1886,3 +1906,6 @@ class Admin:
|
||||||
self.moodle.delete_cohorts(cohort)
|
self.moodle.delete_cohorts(cohort)
|
||||||
self.nextcloud.delete_group(gid)
|
self.nextcloud.delete_group(gid)
|
||||||
self.resync_data()
|
self.resync_data()
|
||||||
|
|
||||||
|
def set_nextcloud_user_mail(self, data):
|
||||||
|
self.nextcloud.set_user_mail(data)
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import logging as log
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from flask import jsonify, request
|
||||||
|
|
||||||
|
from admin import app
|
||||||
|
|
||||||
|
content_type = {"Content-Type": "application/json"}
|
||||||
|
ex = {
|
||||||
|
"bad_request": {
|
||||||
|
"error": {
|
||||||
|
"error": "bad_request",
|
||||||
|
"msg": "Bad request",
|
||||||
|
},
|
||||||
|
"status_code": 400,
|
||||||
|
},
|
||||||
|
"unauthorized": {
|
||||||
|
"error": {
|
||||||
|
"error": "unauthorized",
|
||||||
|
"msg": "Unauthorized",
|
||||||
|
},
|
||||||
|
"status_code": 401,
|
||||||
|
},
|
||||||
|
"forbidden": {
|
||||||
|
"error": {
|
||||||
|
"error": "forbidden",
|
||||||
|
"msg": "Forbidden",
|
||||||
|
},
|
||||||
|
"status_code": 403,
|
||||||
|
},
|
||||||
|
"not_found": {
|
||||||
|
"error": {
|
||||||
|
"error": "not_found",
|
||||||
|
"msg": "Not found",
|
||||||
|
},
|
||||||
|
"status_code": 404,
|
||||||
|
},
|
||||||
|
"conflict": {
|
||||||
|
"error": {
|
||||||
|
"error": "conflict",
|
||||||
|
"msg": "Conflict",
|
||||||
|
},
|
||||||
|
"status_code": 409,
|
||||||
|
},
|
||||||
|
"internal_server": {
|
||||||
|
"error": {
|
||||||
|
"error": "internal_server",
|
||||||
|
"msg": "Internal server error",
|
||||||
|
},
|
||||||
|
"status_code": 500,
|
||||||
|
},
|
||||||
|
"gateway_timeout": {
|
||||||
|
"error": {
|
||||||
|
"error": "gateway_timeout",
|
||||||
|
"msg": "Gateway timeout",
|
||||||
|
},
|
||||||
|
"status_code": 504,
|
||||||
|
},
|
||||||
|
"precondition_required": {
|
||||||
|
"error": {
|
||||||
|
"error": "precondition_required",
|
||||||
|
"msg": "Precondition required",
|
||||||
|
},
|
||||||
|
"status_code": 428,
|
||||||
|
},
|
||||||
|
"insufficient_storage": {
|
||||||
|
"error": {
|
||||||
|
"error": "insufficient_storage",
|
||||||
|
"msg": "Insufficient storage",
|
||||||
|
},
|
||||||
|
"status_code": 507,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
def __init__(self, error="bad_request", description="", debug="", data=None):
|
||||||
|
self.error = ex[error]["error"].copy()
|
||||||
|
self.error["function"] = (
|
||||||
|
inspect.stack()[1][1].split(os.sep)[-1]
|
||||||
|
+ ":"
|
||||||
|
+ str(inspect.stack()[1][2])
|
||||||
|
+ ":"
|
||||||
|
+ inspect.stack()[1][3]
|
||||||
|
)
|
||||||
|
self.error["function_call"] = (
|
||||||
|
inspect.stack()[2][1].split(os.sep)[-1]
|
||||||
|
+ ":"
|
||||||
|
+ str(inspect.stack()[2][2])
|
||||||
|
+ ":"
|
||||||
|
+ inspect.stack()[2][3]
|
||||||
|
)
|
||||||
|
self.error["description"] = str(description)
|
||||||
|
self.error["debug"] = "{}\n\r{}{}".format(
|
||||||
|
"----------- DEBUG START -------------",
|
||||||
|
debug,
|
||||||
|
"----------- DEBUG STOP -------------",
|
||||||
|
)
|
||||||
|
self.error["request"] = (
|
||||||
|
"{}\n{}\r\n{}\r\n\r\n{}{}".format(
|
||||||
|
"----------- REQUEST START -----------",
|
||||||
|
request.method + " " + request.url,
|
||||||
|
"\r\n".join("{}: {}".format(k, v) for k, v in request.headers.items()),
|
||||||
|
request.body if hasattr(request, "body") else "",
|
||||||
|
"----------- REQUEST STOP -----------",
|
||||||
|
)
|
||||||
|
if request
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
self.error["data"] = (
|
||||||
|
"{}\n{}\n{}".format(
|
||||||
|
"----------- DATA START -----------",
|
||||||
|
json.dumps(data, indent=2),
|
||||||
|
"----------- DATA STOP -----------",
|
||||||
|
)
|
||||||
|
if data
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
self.status_code = ex[error]["status_code"]
|
||||||
|
self.content_type = content_type
|
||||||
|
log.debug(
|
||||||
|
"%s - %s - [%s -> %s]\r\n%s\r\n%s\r\n%s"
|
||||||
|
% (
|
||||||
|
error,
|
||||||
|
str(description),
|
||||||
|
self.error["function_call"],
|
||||||
|
self.error["function"],
|
||||||
|
self.error["debug"],
|
||||||
|
self.error["request"],
|
||||||
|
self.error["data"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(Error)
|
||||||
|
def handle_user_error(ex):
|
||||||
|
response = jsonify(ex.error)
|
||||||
|
response.status_code = ex.status_code
|
||||||
|
response.headers = {"content-type": content_type}
|
||||||
|
return response
|
|
@ -24,6 +24,16 @@ from flask_socketio import (
|
||||||
from admin import app
|
from admin import app
|
||||||
|
|
||||||
|
|
||||||
|
def sio_event_send(event, data):
|
||||||
|
app.socketio.emit(
|
||||||
|
event,
|
||||||
|
json.dumps(data),
|
||||||
|
namespace="/sio/events",
|
||||||
|
room="events",
|
||||||
|
)
|
||||||
|
sleep(0.001)
|
||||||
|
|
||||||
|
|
||||||
class Events:
|
class Events:
|
||||||
def __init__(self, title, text="", total=0, table=False, type="info"):
|
def __init__(self, title, text="", total=0, table=False, type="info"):
|
||||||
# notice, info, success, and error
|
# notice, info, success, and error
|
||||||
|
|
|
@ -12,6 +12,7 @@ import yaml
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from keycloak import KeycloakAdmin
|
from keycloak import KeycloakAdmin
|
||||||
|
|
||||||
|
from .api_exceptions import Error
|
||||||
from .helpers import get_recursive_groups, kpath2kpaths
|
from .helpers import get_recursive_groups, kpath2kpaths
|
||||||
from .postgres import Postgres
|
from .postgres import Postgres
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ class KeycloakClient:
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
group=False,
|
group=False,
|
||||||
temporary=True,
|
password_temporary=True,
|
||||||
enabled=True,
|
enabled=True,
|
||||||
):
|
):
|
||||||
# RETURNS string with keycloak user id (the main id in this app)
|
# RETURNS string with keycloak user id (the main id in this app)
|
||||||
|
@ -167,12 +168,20 @@ class KeycloakClient:
|
||||||
"firstName": first,
|
"firstName": first,
|
||||||
"lastName": last,
|
"lastName": last,
|
||||||
"credentials": [
|
"credentials": [
|
||||||
{"type": "password", "value": password, "temporary": temporary}
|
{
|
||||||
|
"type": "password",
|
||||||
|
"value": password,
|
||||||
|
"temporary": password_temporary,
|
||||||
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except:
|
except Exception as e:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
|
raise Error(
|
||||||
|
"conflict",
|
||||||
|
"user/email already exists: " + str(username) + "/" + str(email),
|
||||||
|
)
|
||||||
|
|
||||||
if group:
|
if group:
|
||||||
path = "/" + group if group[1:] != "/" else group
|
path = "/" + group if group[1:] != "/" else group
|
||||||
|
@ -186,11 +195,11 @@ class KeycloakClient:
|
||||||
self.keycloak_admin.group_user_add(uid, gid)
|
self.keycloak_admin.group_user_add(uid, gid)
|
||||||
return uid
|
return uid
|
||||||
|
|
||||||
def update_user_pwd(self, user_id, password, temporary=True):
|
def update_user_pwd(self, user_id, password, password_temporary=True):
|
||||||
# Updates
|
# Updates
|
||||||
payload = {
|
payload = {
|
||||||
"credentials": [
|
"credentials": [
|
||||||
{"type": "password", "value": password, "temporary": temporary}
|
{"type": "password", "value": password, "temporary": password_temporary}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
|
@ -3,6 +3,12 @@ import os
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from admin import app
|
from admin import app
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
from minio import Minio
|
||||||
|
from minio.commonconfig import REPLACE, CopySource
|
||||||
|
from minio.deleteobjects import DeleteObject
|
||||||
|
from requests import get, post
|
||||||
|
|
||||||
legal_path= os.path.join(app.root_path, "static/templates/pages/legal/")
|
legal_path= os.path.join(app.root_path, "static/templates/pages/legal/")
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,45 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from cerberus import Validator, rules_set_registry, schema_registry
|
||||||
|
|
||||||
from admin import app
|
from admin import app
|
||||||
|
|
||||||
|
|
||||||
|
class AdminValidator(Validator):
|
||||||
|
None
|
||||||
|
# def _normalize_default_setter_genid(self, document):
|
||||||
|
# return _parse_string(document["name"])
|
||||||
|
|
||||||
|
# def _normalize_default_setter_genidlower(self, document):
|
||||||
|
# return _parse_string(document["name"]).lower()
|
||||||
|
|
||||||
|
# def _normalize_default_setter_gengroupid(self, document):
|
||||||
|
# return _parse_string(
|
||||||
|
# document["parent_category"] + "-" + document["uid"]
|
||||||
|
# ).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def load_validators(purge_unknown=True):
|
||||||
|
validators = {}
|
||||||
|
schema_path = os.path.join(app.root_path, "schemas")
|
||||||
|
for schema_filename in os.listdir(schema_path):
|
||||||
|
try:
|
||||||
|
with open(os.path.join(schema_path, schema_filename)) as file:
|
||||||
|
schema_yml = file.read()
|
||||||
|
schema = yaml.load(schema_yml, Loader=yaml.FullLoader)
|
||||||
|
validators[schema_filename.split(".")[0]] = AdminValidator(
|
||||||
|
schema, purge_unknown=purge_unknown
|
||||||
|
)
|
||||||
|
except IsADirectoryError:
|
||||||
|
None
|
||||||
|
return validators
|
||||||
|
|
||||||
|
|
||||||
|
app.validators = load_validators()
|
||||||
|
|
||||||
|
|
||||||
class loadConfig:
|
class loadConfig:
|
||||||
def __init__(self, app=None):
|
def __init__(self, app=None):
|
||||||
try:
|
try:
|
||||||
|
@ -34,6 +70,7 @@ class loadConfig:
|
||||||
app.config.setdefault(
|
app.config.setdefault(
|
||||||
"VERIFY", True if os.environ["VERIFY"] == "true" else False
|
"VERIFY", True if os.environ["VERIFY"] == "true" else False
|
||||||
)
|
)
|
||||||
|
app.config.setdefault("API_SECRET", os.environ.get("API_SECRET"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
raise
|
raise
|
||||||
|
|
|
@ -10,6 +10,7 @@ import traceback
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from psycopg2 import sql
|
||||||
|
|
||||||
# from ..lib.log import *
|
# from ..lib.log import *
|
||||||
from admin import app
|
from admin import app
|
||||||
|
@ -518,3 +519,47 @@ class Nextcloud:
|
||||||
# 101 - invalid input data
|
# 101 - invalid input data
|
||||||
# 102 - group already exists
|
# 102 - group already exists
|
||||||
# 103 - failed to add the group
|
# 103 - failed to add the group
|
||||||
|
|
||||||
|
def set_user_mail(self, data):
|
||||||
|
query = """SELECT * FROM "oc_mail_accounts" WHERE "email" = '%s'"""
|
||||||
|
sql_query = sql.SQL(query.format(data["email"]))
|
||||||
|
if not len(self.nextcloud_pg.select(sql_query)):
|
||||||
|
query = """INSERT INTO "oc_mail_accounts" ("user_id","name","email","inbound_host","inbound_port","inbound_ssl_mode","inbound_user","inbound_password","outbound_host","outbound_port","outbound_ssl_mode","outbound_user","outbound_password") VALUES
|
||||||
|
('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s');"""
|
||||||
|
account = [
|
||||||
|
data["user_id"],
|
||||||
|
data["name"],
|
||||||
|
data["email"],
|
||||||
|
data["inbound_host"],
|
||||||
|
data["inbound_port"],
|
||||||
|
data["inbound_ssl_mode"],
|
||||||
|
data["inbound_user"],
|
||||||
|
data["inbound_password"],
|
||||||
|
data["outbound_host"],
|
||||||
|
data["outbound_port"],
|
||||||
|
data["outbound_ssl_mode"],
|
||||||
|
data["outbound_user"],
|
||||||
|
data["outbound_password"],
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
query = """UPDATE "oc_mail_accounts" SET ("user_id","name","email","inbound_host","inbound_port","inbound_ssl_mode","inbound_user","inbound_password","outbound_host","outbound_port","outbound_ssl_mode","outbound_user","outbound_password") =
|
||||||
|
('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') WHERE email = '%s';"""
|
||||||
|
|
||||||
|
account = [
|
||||||
|
data["user_id"],
|
||||||
|
data["name"],
|
||||||
|
data["email"],
|
||||||
|
data["inbound_host"],
|
||||||
|
data["inbound_port"],
|
||||||
|
data["inbound_ssl_mode"],
|
||||||
|
data["inbound_user"],
|
||||||
|
data["inbound_password"],
|
||||||
|
data["outbound_host"],
|
||||||
|
data["outbound_port"],
|
||||||
|
data["outbound_ssl_mode"],
|
||||||
|
data["outbound_user"],
|
||||||
|
data["outbound_password"],
|
||||||
|
data["email"],
|
||||||
|
]
|
||||||
|
sql_query = sql.SQL(query.format(",".join([str(acc) for acc in account])))
|
||||||
|
self.nextcloud_pg.update(sql_query)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: "Api created"
|
||||||
|
parent:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ""
|
|
@ -0,0 +1,34 @@
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
regex: ^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$
|
||||||
|
inbound_host:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
inbound_port:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
inbound_ssl_mode:
|
||||||
|
type: string
|
||||||
|
default: ssl
|
||||||
|
inbound_user:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
outbound_host:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
outbound_port:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
outbound_ssl_mode:
|
||||||
|
type: string
|
||||||
|
default: ssl
|
||||||
|
outbound_user:
|
||||||
|
type: string
|
||||||
|
required: true
|
|
@ -0,0 +1,37 @@
|
||||||
|
mails:
|
||||||
|
type: list
|
||||||
|
schema:
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
regex: ^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$
|
||||||
|
inbound_host:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
inbound_port:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
inbound_ssl_mode:
|
||||||
|
type: string
|
||||||
|
default: ssl
|
||||||
|
inbound_user:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
outbound_host:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
outbound_port:
|
||||||
|
type: integer
|
||||||
|
required: true
|
||||||
|
outbound_ssl_mode:
|
||||||
|
type: string
|
||||||
|
default: ssl
|
||||||
|
outbound_user:
|
||||||
|
type: string
|
||||||
|
required: true
|
|
@ -0,0 +1,34 @@
|
||||||
|
username:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
first:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
last:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
regex: ^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$
|
||||||
|
password:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
password_temporary:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
quota:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
enabled:
|
||||||
|
required: true
|
||||||
|
type: boolean
|
||||||
|
role:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
empty: false
|
||||||
|
groups:
|
||||||
|
required: true
|
||||||
|
type: list
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
first:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
last:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
regex: ^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$
|
||||||
|
password:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
password_temporary:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
quota:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
enabled:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
role:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
empty: false
|
||||||
|
groups:
|
||||||
|
required: false
|
||||||
|
type: list
|
||||||
|
|
|
@ -1,545 +1,342 @@
|
||||||
#!flask/bin/python
|
#!flask/bin/python
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
import concurrent.futures
|
|
||||||
import json
|
import json
|
||||||
import logging as log
|
import logging as log
|
||||||
import os
|
import os
|
||||||
import re
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# import Queue
|
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from uuid import uuid4
|
|
||||||
|
|
||||||
from flask import Response, jsonify, redirect, render_template, request, url_for
|
from flask import request
|
||||||
from flask_login import current_user, login_required
|
|
||||||
|
|
||||||
from admin import app
|
from admin import app
|
||||||
|
|
||||||
from ..lib.helpers import system_group
|
from ..lib.api_exceptions import Error
|
||||||
from .decorators import is_admin
|
from .decorators import has_token
|
||||||
|
|
||||||
threads = {"external": None}
|
|
||||||
# q = Queue.Queue()
|
|
||||||
|
|
||||||
from keycloak.exceptions import KeycloakGetError
|
|
||||||
|
|
||||||
from ..lib.dashboard import Dashboard
|
|
||||||
from ..lib.exceptions import UserExists, UserNotFound
|
|
||||||
|
|
||||||
dashboard = Dashboard()
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/sysadmin/api/resync")
|
## LISTS
|
||||||
@app.route("/api/resync")
|
@app.route("/ddapi/users", methods=["GET"])
|
||||||
@login_required
|
@has_token
|
||||||
def resync():
|
def ddapi_users():
|
||||||
return (
|
if request.method == "GET":
|
||||||
json.dumps(app.admin.resync_data()),
|
sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k["username"])
|
||||||
200,
|
users = []
|
||||||
{"Content-Type": "application/json"},
|
for user in sorted_users:
|
||||||
)
|
users.append(user_parser(user))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/users", methods=["GET", "PUT"])
|
|
||||||
@app.route("/api/users/<provider>", methods=["POST", "PUT", "GET", "DELETE"])
|
|
||||||
@login_required
|
|
||||||
def users(provider=False):
|
|
||||||
if request.method == "DELETE":
|
|
||||||
if current_user.role != "admin":
|
|
||||||
return json.dumps({}), 301, {"Content-Type": "application/json"}
|
|
||||||
if provider == "keycloak":
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.delete_keycloak_users()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if provider == "nextcloud":
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.delete_nextcloud_users()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if provider == "moodle":
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.delete_moodle_users()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if request.method == "POST":
|
|
||||||
if current_user.role != "admin":
|
|
||||||
return json.dumps({}), 301, {"Content-Type": "application/json"}
|
|
||||||
if provider == "moodle":
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.sync_to_moodle()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if provider == "nextcloud":
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.sync_to_nextcloud()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if request.method == "PUT" and not provider:
|
|
||||||
if current_user.role != "admin":
|
|
||||||
return json.dumps({}), 301, {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
if "external" in threads.keys():
|
|
||||||
if threads["external"] is not None and threads["external"].is_alive():
|
|
||||||
return (
|
|
||||||
json.dumps(
|
|
||||||
{"msg": "Precondition failed: already working with users"}
|
|
||||||
),
|
|
||||||
412,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
threads["external"] = None
|
|
||||||
try:
|
|
||||||
threads["external"] = threading.Thread(
|
|
||||||
target=app.admin.update_users_from_keycloak, args=()
|
|
||||||
)
|
|
||||||
threads["external"].start()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
return (
|
|
||||||
json.dumps({"msg": "Add user error."}),
|
|
||||||
500,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
# return json.dumps(app.admin.update_users_from_keycloak()), 200, {'Content-Type': 'application/json'}
|
|
||||||
|
|
||||||
users = app.admin.get_mix_users()
|
|
||||||
if current_user.role != "admin":
|
|
||||||
for user in users:
|
|
||||||
user["keycloak_groups"] = [
|
|
||||||
g for g in user["keycloak_groups"] if not system_group(g)
|
|
||||||
]
|
|
||||||
return json.dumps(users), 200, {"Content-Type": "application/json"}
|
return json.dumps(users), 200, {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/users_bulk/<action>", methods=["PUT"])
|
@app.route("/ddapi/users/filter", methods=["POST"])
|
||||||
@login_required
|
@has_token
|
||||||
def users_bulk(action):
|
def ddapi_users_search():
|
||||||
data = request.get_json(force=True)
|
|
||||||
if request.method == "PUT":
|
|
||||||
if action == "enable":
|
|
||||||
if "external" in threads.keys():
|
|
||||||
if threads["external"] is not None and threads["external"].is_alive():
|
|
||||||
return (
|
|
||||||
json.dumps(
|
|
||||||
{"msg": "Precondition failed: already operating users"}
|
|
||||||
),
|
|
||||||
412,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
threads["external"] = None
|
|
||||||
try:
|
|
||||||
threads["external"] = threading.Thread(
|
|
||||||
target=app.admin.enable_users, args=(data,)
|
|
||||||
)
|
|
||||||
threads["external"].start()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
return (
|
|
||||||
json.dumps({"msg": "Enable users error."}),
|
|
||||||
500,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if action == "disable":
|
|
||||||
if "external" in threads.keys():
|
|
||||||
if threads["external"] is not None and threads["external"].is_alive():
|
|
||||||
return (
|
|
||||||
json.dumps(
|
|
||||||
{"msg": "Precondition failed: already operating users"}
|
|
||||||
),
|
|
||||||
412,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
threads["external"] = None
|
|
||||||
try:
|
|
||||||
threads["external"] = threading.Thread(
|
|
||||||
target=app.admin.disable_users, args=(data,)
|
|
||||||
)
|
|
||||||
threads["external"].start()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
return (
|
|
||||||
json.dumps({"msg": "Disabling users error."}),
|
|
||||||
500,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if action == "delete":
|
|
||||||
if "external" in threads.keys():
|
|
||||||
if threads["external"] is not None and threads["external"].is_alive():
|
|
||||||
return (
|
|
||||||
json.dumps(
|
|
||||||
{"msg": "Precondition failed: already operating users"}
|
|
||||||
),
|
|
||||||
412,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
threads["external"] = None
|
|
||||||
try:
|
|
||||||
threads["external"] = threading.Thread(
|
|
||||||
target=app.admin.delete_users, args=(data,)
|
|
||||||
)
|
|
||||||
threads["external"].start()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
return (
|
|
||||||
json.dumps({"msg": "Deleting users error."}),
|
|
||||||
500,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
return json.dumps({}), 405, {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
|
|
||||||
# Update pwd
|
|
||||||
@app.route("/api/user_password", methods=["GET"])
|
|
||||||
@app.route("/api/user_password/<userid>", methods=["PUT"])
|
|
||||||
@login_required
|
|
||||||
def user_password(userid=False):
|
|
||||||
if request.method == "GET":
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.get_dice_pwd()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if request.method == "PUT":
|
|
||||||
data = request.get_json(force=True)
|
|
||||||
password = data["password"]
|
|
||||||
temporary = data.get("temporary", True)
|
|
||||||
try:
|
|
||||||
res = app.admin.user_update_password(userid, password, temporary)
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
except KeycloakGetError as e:
|
|
||||||
log.error(e.error_message.decode("utf-8"))
|
|
||||||
return (
|
|
||||||
json.dumps({"msg": "Update password error."}),
|
|
||||||
500,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
return json.dumps({}), 405, {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
|
|
||||||
# User
|
|
||||||
@app.route("/api/user", methods=["POST"])
|
|
||||||
@app.route("/api/user/<userid>", methods=["PUT", "GET", "DELETE"])
|
|
||||||
@login_required
|
|
||||||
def user(userid=None):
|
|
||||||
if request.method == "DELETE":
|
|
||||||
app.admin.delete_user(userid)
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
data = request.get_json(force=True)
|
data = request.get_json(force=True)
|
||||||
if app.admin.get_user_username(data["username"]):
|
if not data.get("text"):
|
||||||
return (
|
raise Error("bad_request", "Incorrect data requested.")
|
||||||
json.dumps({"msg": "Add user error: already exists."}),
|
users = app.admin.get_mix_users()
|
||||||
409,
|
result = [user_parser(user) for user in filter_users(users, data["text"])]
|
||||||
{"Content-Type": "application/json"},
|
sorted_result = sorted(result, key=lambda k: k["id"])
|
||||||
)
|
return json.dumps(sorted_result), 200, {"Content-Type": "application/json"}
|
||||||
data["enabled"] = True if data.get("enabled", False) else False
|
|
||||||
data["quota"] = data["quota"] if data["quota"] != "false" else False
|
|
||||||
data["groups"] = data["groups"] if data.get("groups", False) else []
|
|
||||||
if "external" in threads.keys():
|
|
||||||
if threads["external"] is not None and threads["external"].is_alive():
|
|
||||||
return (
|
|
||||||
json.dumps({"msg": "Precondition failed: already adding users"}),
|
|
||||||
412,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
threads["external"] = None
|
|
||||||
try:
|
|
||||||
threads["external"] = threading.Thread(
|
|
||||||
target=app.admin.add_user, args=(data,)
|
|
||||||
)
|
|
||||||
threads["external"].start()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
return (
|
|
||||||
json.dumps({"msg": "Add user error."}),
|
|
||||||
500,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
if request.method == "PUT":
|
|
||||||
data = request.get_json(force=True)
|
@app.route("/ddapi/groups", methods=["GET"])
|
||||||
data["enabled"] = True if data.get("enabled", False) else False
|
@has_token
|
||||||
data["groups"] = data["groups"] if data.get("groups", False) else []
|
def ddapi_groups():
|
||||||
data["roles"] = [data.pop("role-keycloak")]
|
|
||||||
try:
|
|
||||||
app.admin.user_update(data)
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
except UserNotFound:
|
|
||||||
return (
|
|
||||||
json.dumps({"msg": "User not found."}),
|
|
||||||
404,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
if request.method == "DELETE":
|
|
||||||
pass
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
user = app.admin.get_user(userid)
|
sorted_groups = sorted(app.admin.get_mix_groups(), key=lambda k: k["name"])
|
||||||
if not user:
|
groups = []
|
||||||
return (
|
for group in sorted_groups:
|
||||||
json.dumps({"msg": "User not found."}),
|
groups.append(group_parser(group))
|
||||||
404,
|
return json.dumps(groups), 200, {"Content-Type": "application/json"}
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
return json.dumps(user), 200, {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/roles")
|
@app.route("/ddapi/group/users", methods=["POST"])
|
||||||
@login_required
|
@has_token
|
||||||
def roles():
|
def ddapi_group_users():
|
||||||
sorted_roles = sorted(app.admin.get_roles(), key=lambda k: k["name"])
|
|
||||||
if current_user.role != "admin":
|
|
||||||
sorted_roles = [sr for sr in sorted_roles if sr["name"] != "admin"]
|
|
||||||
return json.dumps(sorted_roles), 200, {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/group", methods=["POST", "DELETE"])
|
|
||||||
@app.route("/api/group/<group_id>", methods=["PUT", "GET", "DELETE"])
|
|
||||||
@login_required
|
|
||||||
def group(group_id=False):
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
data = request.get_json(force=True)
|
data = request.get_json(force=True)
|
||||||
data["parent"] = data["parent"] if data["parent"] != "" else None
|
sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k["username"])
|
||||||
return (
|
if data.get("id"):
|
||||||
json.dumps(app.admin.add_group(data)),
|
group_users = [
|
||||||
200,
|
user_parser(user)
|
||||||
{"Content-Type": "application/json"},
|
for user in sorted_users
|
||||||
)
|
if data.get("id") in user["keycloak_groups"]
|
||||||
if request.method == "DELETE":
|
]
|
||||||
|
elif data.get("path"):
|
||||||
try:
|
try:
|
||||||
data = request.get_json(force=True)
|
name = [
|
||||||
|
g["name"]
|
||||||
|
for g in app.admin.get_mix_groups()
|
||||||
|
if g["path"] == data.get("path")
|
||||||
|
][0]
|
||||||
|
group_users = [
|
||||||
|
user_parser(user)
|
||||||
|
for user in sorted_users
|
||||||
|
if name in user["keycloak_groups"]
|
||||||
|
]
|
||||||
except:
|
except:
|
||||||
data = False
|
raise Error("not_found", "Group path not found in system")
|
||||||
|
elif data.get("keycloak_id"):
|
||||||
if data:
|
try:
|
||||||
res = app.admin.delete_group_by_path(data["path"])
|
name = [
|
||||||
|
g["name"]
|
||||||
|
for g in app.admin.get_mix_groups()
|
||||||
|
if g["id"] == data.get("keycloak_id")
|
||||||
|
][0]
|
||||||
|
group_users = [
|
||||||
|
user_parser(user)
|
||||||
|
for user in sorted_users
|
||||||
|
if name in user["keycloak_groups"]
|
||||||
|
]
|
||||||
|
except:
|
||||||
|
raise Error("not_found", "Group keycloak_id not found in system")
|
||||||
else:
|
else:
|
||||||
res = app.admin.delete_group_by_id(group_id)
|
raise Error("bad_request", "Incorrect data requested.")
|
||||||
return json.dumps(res), 200, {"Content-Type": "application/json"}
|
return json.dumps(group_users), 200, {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/groups")
|
@app.route("/ddapi/roles", methods=["GET"])
|
||||||
@app.route("/api/groups/<provider>", methods=["POST", "PUT", "GET", "DELETE"])
|
@has_token
|
||||||
@login_required
|
def ddapi_roles():
|
||||||
def groups(provider=False):
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
sorted_groups = sorted(app.admin.get_mix_groups(), key=lambda k: str(k["name"]))
|
roles = []
|
||||||
if current_user.role != "admin":
|
for role in sorted(app.admin.get_roles(), key=lambda k: k["name"]):
|
||||||
## internal groups should be avoided as are assigned with the role
|
log.error(role)
|
||||||
sorted_groups = [sg for sg in sorted_groups if not system_group(sg["name"])]
|
roles.append(
|
||||||
else:
|
|
||||||
sorted_groups = [sg for sg in sorted_groups]
|
|
||||||
return json.dumps(sorted_groups), 200, {"Content-Type": "application/json"}
|
|
||||||
if request.method == "DELETE":
|
|
||||||
if provider == "keycloak":
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.delete_keycloak_groups()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
### SYSADM USERS ONLY
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/external", methods=["POST", "PUT", "GET", "DELETE"])
|
|
||||||
@login_required
|
|
||||||
def external():
|
|
||||||
if "external" in threads.keys():
|
|
||||||
if threads["external"] is not None and threads["external"].is_alive():
|
|
||||||
return json.dumps({}), 301, {"Content-Type": "application/json"}
|
|
||||||
else:
|
|
||||||
threads["external"] = None
|
|
||||||
|
|
||||||
if request.method == "POST":
|
|
||||||
data = request.get_json(force=True)
|
|
||||||
if data["format"] == "json-ga":
|
|
||||||
threads["external"] = threading.Thread(
|
|
||||||
target=app.admin.upload_json_ga, args=(data,)
|
|
||||||
)
|
|
||||||
threads["external"].start()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
if data["format"] == "csv-ug":
|
|
||||||
valid = check_upload_errors(data)
|
|
||||||
if valid["pass"]:
|
|
||||||
threads["external"] = threading.Thread(
|
|
||||||
target=app.admin.upload_csv_ug, args=(data,)
|
|
||||||
)
|
|
||||||
threads["external"].start()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
else:
|
|
||||||
return json.dumps(valid), 422, {"Content-Type": "application/json"}
|
|
||||||
if request.method == "PUT":
|
|
||||||
data = request.get_json(force=True)
|
|
||||||
threads["external"] = threading.Thread(
|
|
||||||
target=app.admin.sync_external, args=(data,)
|
|
||||||
)
|
|
||||||
threads["external"].start()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
if request.method == "DELETE":
|
|
||||||
print("RESET")
|
|
||||||
app.admin.reset_external()
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
return json.dumps({}), 500, {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/external/users")
|
|
||||||
@login_required
|
|
||||||
def external_users_list():
|
|
||||||
while threads["external"] is not None and threads["external"].is_alive():
|
|
||||||
time.sleep(0.5)
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.get_external_users()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/external/groups")
|
|
||||||
@login_required
|
|
||||||
def external_groups_list():
|
|
||||||
while threads["external"] is not None and threads["external"].is_alive():
|
|
||||||
time.sleep(0.5)
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.get_external_groups()),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/external/roles", methods=["PUT"])
|
|
||||||
@login_required
|
|
||||||
def external_roles():
|
|
||||||
if request.method == "PUT":
|
|
||||||
return (
|
|
||||||
json.dumps(app.admin.external_roleassign(request.get_json(force=True))),
|
|
||||||
200,
|
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def check_upload_errors(data):
|
|
||||||
email_regex = r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
|
|
||||||
for u in data["data"]:
|
|
||||||
try:
|
|
||||||
user_groups = [g.strip() for g in u["groups"].split(",")]
|
|
||||||
except:
|
|
||||||
resp = {
|
|
||||||
"pass": False,
|
|
||||||
"msg": "User " + u["username"] + " has invalid groups: " + u["groups"],
|
|
||||||
}
|
|
||||||
log.error(resp)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
if not re.fullmatch(email_regex, u["email"]):
|
|
||||||
resp = {
|
|
||||||
"pass": False,
|
|
||||||
"msg": "User " + u["username"] + " has invalid email: " + u["email"],
|
|
||||||
}
|
|
||||||
log.error(resp)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
if u["role"] not in ["admin", "manager", "teacher", "student"]:
|
|
||||||
if u["role"] == "":
|
|
||||||
resp = {
|
|
||||||
"pass": False,
|
|
||||||
"msg": "User " + u["username"] + " has no role assigned!",
|
|
||||||
}
|
|
||||||
log.error(resp)
|
|
||||||
return resp
|
|
||||||
resp = {
|
|
||||||
"pass": False,
|
|
||||||
"msg": "User " + u["username"] + " has invalid role: " + u["role"],
|
|
||||||
}
|
|
||||||
log.error(resp)
|
|
||||||
return resp
|
|
||||||
return {"pass": True, "msg": ""}
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/dashboard/<item>", methods=["PUT"])
|
|
||||||
# @login_required
|
|
||||||
def dashboard_put(item):
|
|
||||||
if item == "colours":
|
|
||||||
try:
|
|
||||||
data = request.get_json(force=True)
|
|
||||||
dashboard.update_colours(data)
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
return json.dumps({"colours": data}), 200, {"Content-Type": "application/json"}
|
|
||||||
if item == "menu":
|
|
||||||
try:
|
|
||||||
data = request.get_json(force=True)
|
|
||||||
dashboard.update_menu(data)
|
|
||||||
except:
|
|
||||||
log.error(traceback.format_exc())
|
|
||||||
return json.dumps(data), 200, {"Content-Type": "application/json"}
|
|
||||||
if item == "logo":
|
|
||||||
dashboard.update_logo(request.files["croppedImage"])
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
if item == "background":
|
|
||||||
dashboard.update_background(request.files["croppedImage"])
|
|
||||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
|
||||||
return (
|
|
||||||
json.dumps(
|
|
||||||
{
|
{
|
||||||
"error": "update_error",
|
"keycloak_id": role["id"],
|
||||||
"msg": "Error updating item " + item + "\n" + traceback.format_exc(),
|
"id": role["name"],
|
||||||
|
"name": role["name"],
|
||||||
|
"description": role.get("description", ""),
|
||||||
}
|
}
|
||||||
),
|
)
|
||||||
500,
|
return json.dumps(roles), 200, {"Content-Type": "application/json"}
|
||||||
{"Content-Type": "application/json"},
|
|
||||||
|
|
||||||
|
@app.route("/ddapi/role/users", methods=["POST"])
|
||||||
|
@has_token
|
||||||
|
def ddapi_role_users():
|
||||||
|
if request.method == "POST":
|
||||||
|
data = request.get_json(force=True)
|
||||||
|
sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k["username"])
|
||||||
|
if data.get("id", data.get("name")):
|
||||||
|
role_users = [
|
||||||
|
user_parser(user)
|
||||||
|
for user in sorted_users
|
||||||
|
if data.get("id", data.get("name")) in user["roles"]
|
||||||
|
]
|
||||||
|
elif data.get("keycloak_id"):
|
||||||
|
try:
|
||||||
|
id = [
|
||||||
|
r["id"]
|
||||||
|
for r in app.admin.get_roles()
|
||||||
|
if r["id"] == data.get("keycloak_id")
|
||||||
|
][0]
|
||||||
|
role_users = [
|
||||||
|
user_parser(user) for user in sorted_users if id in user["roles"]
|
||||||
|
]
|
||||||
|
except:
|
||||||
|
raise Error("not_found", "Role keycloak_id not found in system")
|
||||||
|
else:
|
||||||
|
raise Error("bad_request", "Incorrect data requested.")
|
||||||
|
return json.dumps(role_users), 200, {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
|
## INDIVIDUAL ACTIONS
|
||||||
|
@app.route("/ddapi/user", methods=["POST"])
|
||||||
|
@app.route("/ddapi/user/<user_ddid>", methods=["PUT", "GET", "DELETE"])
|
||||||
|
@has_token
|
||||||
|
def ddapi_user(user_ddid=None):
|
||||||
|
if request.method == "GET":
|
||||||
|
user = app.admin.get_user_username(user_ddid)
|
||||||
|
if not user:
|
||||||
|
raise Error("not_found", "User id not found")
|
||||||
|
return json.dumps(user_parser(user)), 200, {"Content-Type": "application/json"}
|
||||||
|
if request.method == "DELETE":
|
||||||
|
user = app.admin.get_user_username(user_ddid)
|
||||||
|
if not user:
|
||||||
|
raise Error("not_found", "User id not found")
|
||||||
|
app.admin.delete_user(user["id"])
|
||||||
|
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
||||||
|
if request.method == "POST":
|
||||||
|
data = request.get_json(force=True)
|
||||||
|
if not app.validators["user"].validate(data):
|
||||||
|
raise Error(
|
||||||
|
"bad_request",
|
||||||
|
"Data validation for user failed: ",
|
||||||
|
+str(app.validators["user"].errors),
|
||||||
|
traceback.format_exc(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if app.admin.get_user_username(data["username"]):
|
||||||
@app.route("/api/legal/<item>", methods=["GET", "POST"])
|
raise Error("conflict", "User id already exists")
|
||||||
# @login_required
|
data = app.validators["user"].normalized(data)
|
||||||
def legal_put(item):
|
keycloak_id = app.admin.add_user(data)
|
||||||
if request.method == "GET":
|
if not keycloak_id:
|
||||||
if item == "legal":
|
raise Error(
|
||||||
lang = request.args.get("lang")
|
"precondition_required",
|
||||||
|
"Not all user groups already in system. Please create user groups before adding user.",
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
json.dumps({"html": "<b>Legal</b><br>This works! in lang: " + lang}),
|
json.dumps({"keycloak_id": keycloak_id}),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
|
||||||
|
if request.method == "PUT":
|
||||||
|
user = app.admin.get_user_username(user_ddid)
|
||||||
|
if not user:
|
||||||
|
raise Error("not_found", "User id not found")
|
||||||
|
data = request.get_json(force=True)
|
||||||
|
if not app.validators["user_update"].validate(data):
|
||||||
|
raise Error(
|
||||||
|
"bad_request",
|
||||||
|
"Data validation for user failed: "
|
||||||
|
+ str(app.validators["user_update"].errors),
|
||||||
|
traceback.format_exc(),
|
||||||
|
)
|
||||||
|
data = {**user, **data}
|
||||||
|
data = app.validators["user_update"].normalized(data)
|
||||||
|
data = {**data, **{"username": user_ddid}}
|
||||||
|
data["roles"] = [data.pop("role")]
|
||||||
|
data["firstname"] = data.pop("first")
|
||||||
|
data["lastname"] = data.pop("last")
|
||||||
|
app.admin.user_update(data)
|
||||||
|
if data.get("password"):
|
||||||
|
app.admin.user_update_password(
|
||||||
|
user["id"], data["password"], data["password_temporary"]
|
||||||
|
)
|
||||||
|
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/ddapi/username/<old_user_ddid>/<new_user_did>", methods=["PUT"])
|
||||||
|
@has_token
|
||||||
|
def ddapi_username(old_user_ddid, new_user_did):
|
||||||
|
user = app.admin.get_user_username(user_ddid)
|
||||||
|
if not user:
|
||||||
|
raise Error("not_found", "User id not found")
|
||||||
|
# user = app.admin.update_user_username(old_user_ddid,new_user_did)
|
||||||
|
return json.dumps("Not implemented yet!"), 419, {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/ddapi/group", methods=["POST"])
|
||||||
|
@app.route("/ddapi/group/<id>", methods=["GET", "POST", "DELETE"])
|
||||||
|
# @app.route("/api/group/<group_id>", methods=["PUT", "GET", "DELETE"])
|
||||||
|
@has_token
|
||||||
|
def ddapi_group(id=None):
|
||||||
|
if request.method == "GET":
|
||||||
|
group = app.admin.get_group_by_name(id)
|
||||||
|
if not group:
|
||||||
|
Error("not found", "Group id not found")
|
||||||
|
return (
|
||||||
|
json.dumps(group_parser(group)),
|
||||||
200,
|
200,
|
||||||
{"Content-Type": "application/json"},
|
{"Content-Type": "application/json"},
|
||||||
)
|
)
|
||||||
# if item == "privacy":
|
|
||||||
# return json.dumps({ "html": "<b>Privacy policy</b><br>This works!"}), 200, {'Content-Type': 'application/json'}
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if item == "legal":
|
data = request.get_json(force=True)
|
||||||
data = None
|
if not app.validators["group"].validate(data):
|
||||||
try:
|
raise Error(
|
||||||
data = request.json
|
"bad_request",
|
||||||
html = data["html"]
|
"Data validation for group failed: "
|
||||||
lang = data["lang"]
|
+ str(app.validators["group"].errors),
|
||||||
except:
|
traceback.format_exc(),
|
||||||
log.error(traceback.format_exc())
|
)
|
||||||
return json.dumps(data), 200, {"Content-Type": "application/json"}
|
data = app.validators["group"].normalized(data)
|
||||||
# if item == "privacy":
|
data["parent"] = data["parent"] if data["parent"] != "" else None
|
||||||
# data = None
|
|
||||||
# try:
|
if app.admin.get_group_by_name(id):
|
||||||
# data = request.json
|
raise Error("conflict", "Group id already exists")
|
||||||
# html = data["html"]
|
|
||||||
# lang = data["lang"]
|
path = app.admin.add_group(data)
|
||||||
# except:
|
# log.error(path)
|
||||||
# log.error(traceback.format_exc())
|
# keycloak_id = app.admin.get_group_by_name(id)["id"]
|
||||||
# return json.dumps(data), 200, {'Content-Type': 'application/json'}
|
# log.error()
|
||||||
|
return (
|
||||||
|
json.dumps({"keycloak_id": None}),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
if request.method == "DELETE":
|
||||||
|
group = app.admin.get_group_by_name(id)
|
||||||
|
if not group:
|
||||||
|
raise Error("not_found", "Group id not found")
|
||||||
|
app.admin.delete_group_by_id(group["id"])
|
||||||
|
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/ddapi/user_mail", methods=["POST"])
|
||||||
|
@app.route("/ddapi/user_mail/<id>", methods=["GET", "DELETE"])
|
||||||
|
@has_token
|
||||||
|
def ddapi_user_mail(id=None):
|
||||||
|
if request.method == "GET":
|
||||||
|
return (
|
||||||
|
json.dumps("Not implemented yet"),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
if request.method == "POST":
|
||||||
|
data = request.get_json(force=True)
|
||||||
|
|
||||||
|
# if not app.validators["mails"].validate(data):
|
||||||
|
# raise Error(
|
||||||
|
# "bad_request",
|
||||||
|
# "Data validation for mail failed: "
|
||||||
|
# + str(app.validators["mail"].errors),
|
||||||
|
# traceback.format_exc(),
|
||||||
|
# )
|
||||||
|
for user in data:
|
||||||
|
if not app.validators["mail"].validate(user):
|
||||||
|
raise Error(
|
||||||
|
"bad_request",
|
||||||
|
"Data validation for mail failed: "
|
||||||
|
+ str(app.validators["mail"].errors),
|
||||||
|
traceback.format_exc(),
|
||||||
|
)
|
||||||
|
for user in data:
|
||||||
|
log.info("Added user email")
|
||||||
|
app.admin.set_nextcloud_user_mail(user)
|
||||||
|
return (
|
||||||
|
json.dumps("Users emails updated"),
|
||||||
|
200,
|
||||||
|
{"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def user_parser(user):
|
||||||
|
return {
|
||||||
|
"keycloak_id": user["id"],
|
||||||
|
"id": user["username"],
|
||||||
|
"username": user["username"],
|
||||||
|
"enabled": user["enabled"],
|
||||||
|
"first": user["first"],
|
||||||
|
"last": user["last"],
|
||||||
|
"role": user["roles"][0] if len(user["roles"]) else None,
|
||||||
|
"email": user["email"],
|
||||||
|
"groups": user.get("groups", user["keycloak_groups"]),
|
||||||
|
"quota": user["quota"],
|
||||||
|
"quota_used_bytes": user["quota_used_bytes"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def group_parser(group):
|
||||||
|
return {
|
||||||
|
"keycloak_id": group["id"],
|
||||||
|
"id": group["name"],
|
||||||
|
"name": group["name"].split(".")[-1],
|
||||||
|
"path": group["path"],
|
||||||
|
"description": group.get("description", ""),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def filter_users(users, text):
|
||||||
|
return [
|
||||||
|
user
|
||||||
|
for user in users
|
||||||
|
if text in user["username"]
|
||||||
|
or text in user["first"]
|
||||||
|
or text in user["last"]
|
||||||
|
or text in user["email"]
|
||||||
|
]
|
||||||
|
|
|
@ -518,6 +518,7 @@ def dashboard_put(item):
|
||||||
{"Content-Type": "application/json"},
|
{"Content-Type": "application/json"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/legal/<item>", methods=["GET"])
|
@app.route("/api/legal/<item>", methods=["GET"])
|
||||||
# @login_required
|
# @login_required
|
||||||
def legal_get(item):
|
def legal_get(item):
|
||||||
|
|
|
@ -137,3 +137,10 @@ def web_sysadmin_external():
|
||||||
return render_template(
|
return render_template(
|
||||||
"pages/sysadmin/external.html", title="External", nav="External"
|
"pages/sysadmin/external.html", title="External", nav="External"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/sockettest")
|
||||||
|
def web_sockettest():
|
||||||
|
return render_template(
|
||||||
|
"pages/sockettest.html", title="Sockettest Users", nav="SysAdminUsers"
|
||||||
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import json
|
import json
|
||||||
import logging as log
|
import logging as log
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -17,6 +18,7 @@ from .decorators import is_internal
|
||||||
@app.route("/api/internal/users", methods=["GET"])
|
@app.route("/api/internal/users", methods=["GET"])
|
||||||
@is_internal
|
@is_internal
|
||||||
def internal_users():
|
def internal_users():
|
||||||
|
log.error(socket.gethostbyname("isard-apps-wordpress"))
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k["username"])
|
sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k["username"])
|
||||||
# group_users = [user for user in sorted_users if data['path'] in user['keycloak_groups']]
|
# group_users = [user for user in sorted_users if data['path'] in user['keycloak_groups']]
|
|
@ -1,11 +1,17 @@
|
||||||
#!flask/bin/python
|
#!flask/bin/python
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging as log
|
||||||
|
import os
|
||||||
import socket
|
import socket
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from flask import redirect, request, url_for
|
from flask import redirect, request, url_for
|
||||||
from flask_login import current_user, logout_user
|
from flask_login import current_user, logout_user
|
||||||
|
from jose import jwt
|
||||||
|
|
||||||
|
from ..auth.tokens import get_header_jwt_payload
|
||||||
|
|
||||||
|
|
||||||
def is_admin(fn):
|
def is_admin(fn):
|
||||||
|
@ -30,7 +36,53 @@ def is_internal(fn):
|
||||||
## but we should check if it is internal net and not haproxy
|
## but we should check if it is internal net and not haproxy
|
||||||
if socket.gethostbyname("isard-apps-wordpress") == remote_addr:
|
if socket.gethostbyname("isard-apps-wordpress") == remote_addr:
|
||||||
return fn(*args, **kwargs)
|
return fn(*args, **kwargs)
|
||||||
logout_user()
|
return (
|
||||||
return redirect(url_for("login"))
|
json.dumps(
|
||||||
|
{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"msg": "Unauthorized access",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
401,
|
||||||
|
{"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
|
||||||
|
return decorated_view
|
||||||
|
|
||||||
|
|
||||||
|
def has_token(fn):
|
||||||
|
@wraps(fn)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
payload = get_header_jwt_payload()
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
def is_internal_or_has_token(fn):
|
||||||
|
@wraps(fn)
|
||||||
|
def decorated_view(*args, **kwargs):
|
||||||
|
remote_addr = (
|
||||||
|
request.headers["X-Forwarded-For"].split(",")[0]
|
||||||
|
if "X-Forwarded-For" in request.headers
|
||||||
|
else request.remote_addr.split(",")[0]
|
||||||
|
)
|
||||||
|
payload = get_header_jwt_payload()
|
||||||
|
|
||||||
|
if socket.gethostbyname("isard-apps-wordpress") == remote_addr:
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
payload = get_header_jwt_payload()
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
|
||||||
|
return decorated_view
|
||||||
|
|
||||||
|
|
||||||
|
def login_or_token(fn):
|
||||||
|
@wraps(fn)
|
||||||
|
def decorated_view(*args, **kwargs):
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
payload = get_header_jwt_payload()
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
|
||||||
return decorated_view
|
return decorated_view
|
||||||
|
|
|
@ -6,7 +6,10 @@ monkey_patch()
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from flask_login import login_required
|
from admin.auth.tokens import get_token_payload
|
||||||
|
from admin.lib.api_exceptions import Error
|
||||||
|
from flask import request
|
||||||
|
from flask_login import current_user, login_required
|
||||||
from flask_socketio import (
|
from flask_socketio import (
|
||||||
SocketIO,
|
SocketIO,
|
||||||
close_room,
|
close_room,
|
||||||
|
@ -26,14 +29,36 @@ app.socketio = SocketIO(app)
|
||||||
@app.socketio.on("connect", namespace="/sio")
|
@app.socketio.on("connect", namespace="/sio")
|
||||||
@login_required
|
@login_required
|
||||||
def socketio_connect():
|
def socketio_connect():
|
||||||
|
if current_user.id:
|
||||||
join_room("admin")
|
join_room("admin")
|
||||||
app.socketio.emit(
|
app.socketio.emit(
|
||||||
"update", json.dumps("Joined admins room"), namespace="/sio", room="admin"
|
"update", json.dumps("Joined admins room"), namespace="/sio", room="admin"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
None
|
||||||
|
|
||||||
|
|
||||||
@app.socketio.on("disconnect", namespace="/sio")
|
@app.socketio.on("disconnect", namespace="/sio")
|
||||||
def socketio_disconnect():
|
def socketio_disconnect():
|
||||||
None
|
leave_room("admin")
|
||||||
|
|
||||||
|
|
||||||
|
@app.socketio.on("connect", namespace="/sio/events")
|
||||||
|
def socketio_connect():
|
||||||
|
jwt = get_token_payload(request.args.get("jwt"))
|
||||||
|
|
||||||
|
join_room("events")
|
||||||
|
app.socketio.emit(
|
||||||
|
"update",
|
||||||
|
json.dumps("Joined events room"),
|
||||||
|
namespace="/sio/events",
|
||||||
|
room="events",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.socketio.on("disconnect", namespace="/sio/events")
|
||||||
|
def socketio_events_disconnect():
|
||||||
|
leave_room("events")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -42,8 +67,8 @@ if __name__ == "__main__":
|
||||||
host="0.0.0.0",
|
host="0.0.0.0",
|
||||||
port=9000,
|
port=9000,
|
||||||
debug=False,
|
debug=False,
|
||||||
|
)
|
||||||
# ssl_context="adhoc",
|
# ssl_context="adhoc",
|
||||||
# async_mode="threading",
|
# async_mode="threading",
|
||||||
) # , logger=logger, engineio_logger=engineio_logger)
|
# ) # , logger=logger, engineio_logger=engineio_logger)
|
||||||
# , cors_allowed_origins="*"
|
# , cors_allowed_origins="*"
|
||||||
# /usr/lib/python3.8/site-packages/certifi
|
|
||||||
|
|
|
@ -0,0 +1,423 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from jose import jwt
|
||||||
|
|
||||||
|
## SETUP
|
||||||
|
domain = "admin.[YOURDOMAIN]"
|
||||||
|
secret = "[your API_SECRET]"
|
||||||
|
## END SETUP
|
||||||
|
|
||||||
|
|
||||||
|
auths = {}
|
||||||
|
dbconn = None
|
||||||
|
base = "https://" + domain + "/ddapi"
|
||||||
|
|
||||||
|
raw_jwt_data = {
|
||||||
|
"exp": datetime.utcnow() + timedelta(minutes=5),
|
||||||
|
"kid": "test",
|
||||||
|
}
|
||||||
|
admin_jwt = jwt.encode(raw_jwt_data, secret, algorithm="HS256")
|
||||||
|
jwt = {"Authorization": "Bearer " + admin_jwt}
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- USUARIS AL SISTEMA")
|
||||||
|
response = requests.get(
|
||||||
|
base + "/users",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print("METHOD: GET, URL: " + base + "/users, STATUS_CODE:" + str(response.status_code))
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text)[:2])
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- USUARIS AL SISTEMA QUE CONTENEN UN TEXT")
|
||||||
|
data = {"text": "alu"}
|
||||||
|
response = requests.post(
|
||||||
|
base + "/users/filter",
|
||||||
|
json=data,
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: POST, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/users/filter, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
+ ", POST DATA:"
|
||||||
|
)
|
||||||
|
pprint(data)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text)[:2])
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- GRUPS AL SISTEMA")
|
||||||
|
response = requests.get(
|
||||||
|
base + "/groups",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print("METHOD: GET, URL: " + base + "/groups, STATUS_CODE:" + str(response.status_code))
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text)[:2])
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- USUARIS DEL GRUP")
|
||||||
|
data = {"id": "test00.classeB"}
|
||||||
|
response = requests.post(
|
||||||
|
base + "/group/users",
|
||||||
|
json=data,
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: POST, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/group/users, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
+ ", POST DATA:"
|
||||||
|
)
|
||||||
|
pprint(data)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text)[:2])
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- ROLS AL SISTEMA")
|
||||||
|
response = requests.get(
|
||||||
|
base + "/roles",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print("METHOD: GET, URL: " + base + "/roles, STATUS_CODE:" + str(response.status_code))
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text)[:2])
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- USUARIS DEL ROL")
|
||||||
|
data = {"id": "teacher"}
|
||||||
|
response = requests.post(
|
||||||
|
base + "/role/users",
|
||||||
|
json=data,
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: POST, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/role/users, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
+ ", POST DATA:"
|
||||||
|
)
|
||||||
|
pprint(data)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text)[:2])
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
print("\nXXXXXXXXXXXXXXXXX ACTIONS ON USER XXXXXXXXXXXXXXXXXXXXXX\n")
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- GET USER")
|
||||||
|
response = requests.get(
|
||||||
|
base + "/user/nou.usuari",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: GET, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/user/nou.usuari, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- DELETE USER")
|
||||||
|
response = requests.delete(
|
||||||
|
base + "/user/nou.usuari",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: DELETE, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/user/nou.usuari, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- POST NEW USER")
|
||||||
|
user = {
|
||||||
|
"username": "nou.usuari",
|
||||||
|
"first": "Nou",
|
||||||
|
"last": "Usuari",
|
||||||
|
"email": "nou.usuari@nodns.com",
|
||||||
|
"password": "1n2n3n4n5n6",
|
||||||
|
"quota": "default",
|
||||||
|
"enabled": True,
|
||||||
|
"role": "student",
|
||||||
|
"groups": ["test00.classeB"],
|
||||||
|
}
|
||||||
|
response = requests.post(
|
||||||
|
base + "/user",
|
||||||
|
json=user,
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: POST, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/user, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
+ ", POST DATA:"
|
||||||
|
)
|
||||||
|
pprint(user)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- UPDATE USER")
|
||||||
|
update_user = {
|
||||||
|
"id": "nou.usuari",
|
||||||
|
"email": "nou.usuari@nodns.com",
|
||||||
|
"enabled": True,
|
||||||
|
"first": "Antic",
|
||||||
|
"groups": ["test00.classeB"],
|
||||||
|
"last": "Usuari",
|
||||||
|
"quota": "default",
|
||||||
|
"quota_used_bytes": "0 MB",
|
||||||
|
"role": "teacher",
|
||||||
|
}
|
||||||
|
response = requests.put(
|
||||||
|
base + "/user/nou.usuari",
|
||||||
|
json=update_user,
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: PUT, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/user/nou.usuari, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
+ ", PUT DATA:"
|
||||||
|
)
|
||||||
|
pprint(update_user)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- GET USER")
|
||||||
|
response = requests.get(
|
||||||
|
base + "/user/nou.usuari",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: GET, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/user/nou.usuari, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- DELETE USER")
|
||||||
|
response = requests.delete(
|
||||||
|
base + "/user/nou.usuari",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: DELETE, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/user/nou.usuari, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
print("\nXXXXXXXXXXXXXXXXX ACTIONS ON GROUP XXXXXXXXXXXXXXXXXXXXXX\n")
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- GET GROUP")
|
||||||
|
response = requests.get(
|
||||||
|
base + "/group/teacher",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: GET, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/group/teacher, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- POST NEW GROUP")
|
||||||
|
group = {"name": "test"}
|
||||||
|
response = requests.post(
|
||||||
|
base + "/group",
|
||||||
|
json=group,
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: POST, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/group, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
+ ", POST DATA:"
|
||||||
|
)
|
||||||
|
pprint(group)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#######################################################################################################################
|
||||||
|
print(" ----- DELETE GROUP")
|
||||||
|
response = requests.delete(
|
||||||
|
base + "/group/test",
|
||||||
|
headers=jwt,
|
||||||
|
verify=True,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"METHOD: DELETE, URL: "
|
||||||
|
+ base
|
||||||
|
+ "/group/test, STATUS_CODE:"
|
||||||
|
+ str(response.status_code)
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("RESPONSE:")
|
||||||
|
pprint(json.loads(response.text))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"ERROR: "
|
||||||
|
+ json.loads(response.text)["error"]
|
||||||
|
+ " DESCRIPTION: "
|
||||||
|
+ json.loads(response.text)["description"]
|
||||||
|
)
|
Loading…
Reference in New Issue