refator(admin): black
parent
53fbbc123a
commit
f87a6c6b2f
|
@ -1,77 +1,96 @@
|
|||
#!flask/bin/python
|
||||
# coding=utf-8
|
||||
|
||||
import os
|
||||
import logging as log
|
||||
import os
|
||||
|
||||
from flask import Flask, send_from_directory, render_template
|
||||
app = Flask(__name__, static_url_path='')
|
||||
app = Flask(__name__, template_folder='static/templates')
|
||||
from flask import Flask, render_template, send_from_directory
|
||||
|
||||
app = Flask(__name__, static_url_path="")
|
||||
app = Flask(__name__, template_folder="static/templates")
|
||||
app.url_map.strict_slashes = False
|
||||
|
||||
'''
|
||||
"""
|
||||
App secret key for encrypting cookies
|
||||
You can generate one with:
|
||||
import os
|
||||
os.urandom(24)
|
||||
And paste it here.
|
||||
'''
|
||||
"""
|
||||
app.secret_key = "Change this key!/\xf7\x83\xbe\x17\xfa\xa3zT\n\\]m\xa6\x8bF\xdd\r\xf7\x9e\x1d\x1f\x14'"
|
||||
|
||||
print('Starting isard-sso api...')
|
||||
print("Starting isard-sso api...")
|
||||
|
||||
from admin.lib.load_config import loadConfig
|
||||
|
||||
try:
|
||||
loadConfig(app)
|
||||
except:
|
||||
print('Could not get environment variables...')
|
||||
print("Could not get environment variables...")
|
||||
|
||||
from admin.lib.postup import Postup
|
||||
|
||||
Postup()
|
||||
|
||||
from admin.lib.admin import Admin
|
||||
app.admin=Admin()
|
||||
|
||||
app.ready=False
|
||||
app.admin = Admin()
|
||||
|
||||
'''
|
||||
app.ready = False
|
||||
|
||||
"""
|
||||
Debug should be removed on production!
|
||||
'''
|
||||
"""
|
||||
if app.debug:
|
||||
log.warning('Debug mode: {}'.format(app.debug))
|
||||
log.warning("Debug mode: {}".format(app.debug))
|
||||
else:
|
||||
log.info('Debug mode: {}'.format(app.debug))
|
||||
log.info("Debug mode: {}".format(app.debug))
|
||||
|
||||
'''
|
||||
"""
|
||||
Serve static files
|
||||
'''
|
||||
@app.route('/build/<path:path>')
|
||||
def send_build(path):
|
||||
return send_from_directory(os.path.join(app.root_path, 'node_modules/gentelella/build'), path)
|
||||
|
||||
@app.route('/vendors/<path:path>')
|
||||
def send_vendors(path):
|
||||
return send_from_directory(os.path.join(app.root_path, 'node_modules/gentelella/vendors'), path)
|
||||
"""
|
||||
|
||||
@app.route('/templates/<path:path>')
|
||||
|
||||
@app.route("/build/<path:path>")
|
||||
def send_build(path):
|
||||
return send_from_directory(
|
||||
os.path.join(app.root_path, "node_modules/gentelella/build"), path
|
||||
)
|
||||
|
||||
|
||||
@app.route("/vendors/<path:path>")
|
||||
def send_vendors(path):
|
||||
return send_from_directory(
|
||||
os.path.join(app.root_path, "node_modules/gentelella/vendors"), path
|
||||
)
|
||||
|
||||
|
||||
@app.route("/templates/<path:path>")
|
||||
def send_templates(path):
|
||||
return send_from_directory(os.path.join(app.root_path, 'templates'), path)
|
||||
return send_from_directory(os.path.join(app.root_path, "templates"), path)
|
||||
|
||||
|
||||
# @app.route('/templates/<path:path>')
|
||||
# def send_templates(path):
|
||||
# return send_from_directory(os.path.join(app.root_path, 'static/templates'), path)
|
||||
|
||||
@app.route('/static/<path:path>')
|
||||
|
||||
@app.route("/static/<path:path>")
|
||||
def send_static_js(path):
|
||||
return send_from_directory(os.path.join(app.root_path, 'static'), path)
|
||||
return send_from_directory(os.path.join(app.root_path, "static"), path)
|
||||
|
||||
@app.route('/avatars/<path:path>')
|
||||
|
||||
@app.route("/avatars/<path:path>")
|
||||
def send_avatars_img(path):
|
||||
return send_from_directory(os.path.join(app.root_path, '../avatars/master-avatars'), path)
|
||||
return send_from_directory(
|
||||
os.path.join(app.root_path, "../avatars/master-avatars"), path
|
||||
)
|
||||
|
||||
@app.route('/custom/<path:path>')
|
||||
|
||||
@app.route("/custom/<path:path>")
|
||||
def send_custom(path):
|
||||
return send_from_directory(os.path.join(app.root_path, '../custom'), path)
|
||||
return send_from_directory(os.path.join(app.root_path, "../custom"), path)
|
||||
|
||||
|
||||
# @app.errorhandler(404)
|
||||
# def not_found_error(error):
|
||||
|
@ -81,15 +100,7 @@ def send_custom(path):
|
|||
# def internal_error(error):
|
||||
# return render_template('page_500.html'), 500
|
||||
|
||||
'''
|
||||
"""
|
||||
Import all views
|
||||
'''
|
||||
from .views import LoginViews
|
||||
from .views import WebViews
|
||||
from .views import ApiViews
|
||||
from .views import InternalViews
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
from .views import ApiViews, InternalViews, LoginViews, WebViews
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from admin import app
|
||||
from flask_login import LoginManager, UserMixin
|
||||
import os
|
||||
|
||||
''' OIDC TESTS '''
|
||||
from flask_login import LoginManager, UserMixin
|
||||
|
||||
from admin import app
|
||||
|
||||
""" OIDC TESTS """
|
||||
# from flask_oidc import OpenIDConnect
|
||||
# app.config.update({
|
||||
# 'SECRET_KEY': 'u\x91\xcf\xfa\x0c\xb9\x95\xe3t\xba2K\x7f\xfd\xca\xa3\x9f\x90\x88\xb8\xee\xa4\xd6\xe4',
|
||||
|
@ -18,7 +20,7 @@ import os
|
|||
# # 'OVERWRITE_REDIRECT_URI': 'https://sso.mydomain.duckdns.org//custom_callback',
|
||||
# # 'OIDC_CALLBACK_ROUTE': '//custom_callback'
|
||||
# oidc = OpenIDConnect(app)
|
||||
''' OIDC TESTS '''
|
||||
""" OIDC TESTS """
|
||||
|
||||
|
||||
login_manager = LoginManager()
|
||||
|
@ -26,31 +28,33 @@ login_manager.init_app(app)
|
|||
login_manager.login_view = "login"
|
||||
|
||||
|
||||
ram_users={
|
||||
os.environ["ADMINAPP_USER"]: {
|
||||
'id': os.environ["ADMINAPP_USER"],
|
||||
'password': os.environ["ADMINAPP_PASSWORD"],
|
||||
'role': 'manager'
|
||||
},
|
||||
os.environ["KEYCLOAK_USER"]: {
|
||||
'id': os.environ["KEYCLOAK_USER"],
|
||||
'password': os.environ["KEYCLOAK_PASSWORD"],
|
||||
'role': 'admin',
|
||||
},
|
||||
os.environ["WORDPRESS_MARIADB_USER"]: {
|
||||
'id': os.environ["WORDPRESS_MARIADB_USER"],
|
||||
'password': os.environ["WORDPRESS_MARIADB_PASSWORD"],
|
||||
'role': 'manager',
|
||||
}
|
||||
ram_users = {
|
||||
os.environ["ADMINAPP_USER"]: {
|
||||
"id": os.environ["ADMINAPP_USER"],
|
||||
"password": os.environ["ADMINAPP_PASSWORD"],
|
||||
"role": "manager",
|
||||
},
|
||||
os.environ["KEYCLOAK_USER"]: {
|
||||
"id": os.environ["KEYCLOAK_USER"],
|
||||
"password": os.environ["KEYCLOAK_PASSWORD"],
|
||||
"role": "admin",
|
||||
},
|
||||
os.environ["WORDPRESS_MARIADB_USER"]: {
|
||||
"id": os.environ["WORDPRESS_MARIADB_USER"],
|
||||
"password": os.environ["WORDPRESS_MARIADB_PASSWORD"],
|
||||
"role": "manager",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, dict):
|
||||
self.id = dict['id']
|
||||
self.username = dict['id']
|
||||
self.password = dict['password']
|
||||
self.role = dict['role']
|
||||
def __init__(self, dict):
|
||||
self.id = dict["id"]
|
||||
self.username = dict["id"]
|
||||
self.password = dict["password"]
|
||||
self.role = dict["role"]
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def user_loader(username):
|
||||
return User(ram_users[username])
|
||||
return User(ram_users[username])
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,49 +1,61 @@
|
|||
from requests import get, post
|
||||
from admin import app
|
||||
|
||||
import logging as log
|
||||
from pprint import pprint
|
||||
import os
|
||||
from pprint import pprint
|
||||
|
||||
from minio import Minio
|
||||
from minio.commonconfig import REPLACE, CopySource
|
||||
from minio.deleteobjects import DeleteObject
|
||||
from requests import get, post
|
||||
|
||||
class Avatars():
|
||||
from admin import app
|
||||
|
||||
|
||||
class Avatars:
|
||||
def __init__(self):
|
||||
self.mclient = Minio(
|
||||
"isard-sso-avatars:9000",
|
||||
access_key="AKIAIOSFODNN7EXAMPLE",
|
||||
secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
secure=False
|
||||
)
|
||||
self.bucket='master-avatars'
|
||||
"isard-sso-avatars:9000",
|
||||
access_key="AKIAIOSFODNN7EXAMPLE",
|
||||
secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
secure=False,
|
||||
)
|
||||
self.bucket = "master-avatars"
|
||||
self._minio_set_realm()
|
||||
# self.update_missing_avatars()
|
||||
|
||||
def add_user_default_avatar(self,userid,role='unknown'):
|
||||
def add_user_default_avatar(self, userid, role="unknown"):
|
||||
self.mclient.fput_object(
|
||||
self.bucket, userid, os.path.join(app.root_path,"../custom/avatars/"+role+'.jpg'),
|
||||
content_type="image/jpeg ",
|
||||
self.bucket,
|
||||
userid,
|
||||
os.path.join(app.root_path, "../custom/avatars/" + role + ".jpg"),
|
||||
content_type="image/jpeg ",
|
||||
)
|
||||
log.warning(
|
||||
" AVATARS: Updated avatar for user " + userid + " with role " + role
|
||||
)
|
||||
log.warning(' AVATARS: Updated avatar for user '+userid+' with role '+role)
|
||||
|
||||
def delete_user_avatar(self,userid):
|
||||
def delete_user_avatar(self, userid):
|
||||
self.minio_delete_object(userid)
|
||||
|
||||
def update_missing_avatars(self,users):
|
||||
sys_roles=['admin','manager','teacher','student']
|
||||
|
||||
def update_missing_avatars(self, users):
|
||||
sys_roles = ["admin", "manager", "teacher", "student"]
|
||||
for u in self.get_users_without_image(users):
|
||||
try:
|
||||
img=[r+'.jpg' for r in sys_roles if r in u['roles']][0]
|
||||
img = [r + ".jpg" for r in sys_roles if r in u["roles"]][0]
|
||||
except:
|
||||
img='unknown.jpg'
|
||||
img = "unknown.jpg"
|
||||
|
||||
self.mclient.fput_object(
|
||||
self.bucket, u['id'], os.path.join(app.root_path,"../custom/avatars/"+img),
|
||||
content_type="image/jpeg ",
|
||||
self.bucket,
|
||||
u["id"],
|
||||
os.path.join(app.root_path, "../custom/avatars/" + img),
|
||||
content_type="image/jpeg ",
|
||||
)
|
||||
log.warning(
|
||||
" AVATARS: Updated avatar for user "
|
||||
+ u["username"]
|
||||
+ " with role "
|
||||
+ img.split(".")[0]
|
||||
)
|
||||
log.warning(' AVATARS: Updated avatar for user '+u['username']+' with role '+img.split('.')[0])
|
||||
|
||||
def _minio_set_realm(self):
|
||||
if not self.mclient.bucket_exists(self.bucket):
|
||||
|
@ -57,14 +69,14 @@ class Avatars():
|
|||
lambda x: DeleteObject(x.object_name),
|
||||
self.mclient.list_objects(self.bucket),
|
||||
)
|
||||
errors=self.mclient.remove_objects(self.bucket, delete_object_list)
|
||||
errors = self.mclient.remove_objects(self.bucket, delete_object_list)
|
||||
for error in errors:
|
||||
log.error(" AVATARS: Error occured when deleting avatar object: "+ error)
|
||||
log.error(" AVATARS: Error occured when deleting avatar object: " + error)
|
||||
|
||||
def minio_delete_object(self,oid):
|
||||
errors=self.mclient.remove_objects(self.bucket, [DeleteObject(oid)])
|
||||
def minio_delete_object(self, oid):
|
||||
errors = self.mclient.remove_objects(self.bucket, [DeleteObject(oid)])
|
||||
for error in errors:
|
||||
log.error(" AVATARS: Error occured when deleting avatar object: "+ error)
|
||||
log.error(" AVATARS: Error occured when deleting avatar object: " + error)
|
||||
|
||||
def get_users_without_image(self,users):
|
||||
return [u for u in users if u['id'] and u['id'] not in self.minio_get_objects()]
|
||||
def get_users_without_image(self, users):
|
||||
return [u for u in users if u["id"] and u["id"] not in self.minio_get_objects()]
|
||||
|
|
|
@ -1,110 +1,166 @@
|
|||
#!flask/bin/python
|
||||
# coding=utf-8
|
||||
from admin import app
|
||||
import logging as log
|
||||
import traceback
|
||||
|
||||
from uuid import uuid4
|
||||
import json
|
||||
from time import sleep
|
||||
import sys,os
|
||||
from flask import render_template, Response, request, redirect, url_for, jsonify
|
||||
from flask_socketio import SocketIO, emit, join_room, leave_room, \
|
||||
close_room, rooms, disconnect, send
|
||||
import base64
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from time import sleep
|
||||
from uuid import uuid4
|
||||
|
||||
class Events():
|
||||
def __init__(self,title,text='',total=0,table=False,type='info'):
|
||||
from flask import Response, jsonify, redirect, render_template, request, url_for
|
||||
from flask_socketio import (
|
||||
SocketIO,
|
||||
close_room,
|
||||
disconnect,
|
||||
emit,
|
||||
join_room,
|
||||
leave_room,
|
||||
rooms,
|
||||
send,
|
||||
)
|
||||
|
||||
from admin import app
|
||||
|
||||
|
||||
class Events:
|
||||
def __init__(self, title, text="", total=0, table=False, type="info"):
|
||||
# notice, info, success, and error
|
||||
self.eid=str(base64.b64encode(os.urandom(32))[:8])
|
||||
self.title=title
|
||||
self.text=text
|
||||
self.total=total
|
||||
self.table=table
|
||||
self.item=0
|
||||
self.type=type
|
||||
self.eid = str(base64.b64encode(os.urandom(32))[:8])
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.total = total
|
||||
self.table = table
|
||||
self.item = 0
|
||||
self.type = type
|
||||
self.create()
|
||||
|
||||
def create(self):
|
||||
log.info('START '+self.eid+': '+self.text)
|
||||
app.socketio.emit('notify-create',
|
||||
json.dumps({'id':self.eid,
|
||||
'title':self.title,
|
||||
'text':self.text,
|
||||
'type':self.type}),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
log.info("START " + self.eid + ": " + self.text)
|
||||
app.socketio.emit(
|
||||
"notify-create",
|
||||
json.dumps(
|
||||
{
|
||||
"id": self.eid,
|
||||
"title": self.title,
|
||||
"text": self.text,
|
||||
"type": self.type,
|
||||
}
|
||||
),
|
||||
namespace="/sio",
|
||||
room="admin",
|
||||
)
|
||||
sleep(0.001)
|
||||
|
||||
def __del__(self):
|
||||
log.info('END '+self.eid+': '+self.text)
|
||||
app.socketio.emit('notify-destroy',
|
||||
json.dumps({'id':self.eid}),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
log.info("END " + self.eid + ": " + self.text)
|
||||
app.socketio.emit(
|
||||
"notify-destroy",
|
||||
json.dumps({"id": self.eid}),
|
||||
namespace="/sio",
|
||||
room="admin",
|
||||
)
|
||||
sleep(0.001)
|
||||
|
||||
def update_text(self,text):
|
||||
self.text=text
|
||||
app.socketio.emit('notify-update',
|
||||
json.dumps({'id':self.eid,
|
||||
'text':self.text,}),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
def update_text(self, text):
|
||||
self.text = text
|
||||
app.socketio.emit(
|
||||
"notify-update",
|
||||
json.dumps(
|
||||
{
|
||||
"id": self.eid,
|
||||
"text": self.text,
|
||||
}
|
||||
),
|
||||
namespace="/sio",
|
||||
room="admin",
|
||||
)
|
||||
sleep(0.001)
|
||||
|
||||
def append_text(self,text):
|
||||
self.text=self.text+'<br>'+text
|
||||
app.socketio.emit('notify-update',
|
||||
json.dumps({'id':self.eid,
|
||||
'text':self.text,}),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
def append_text(self, text):
|
||||
self.text = self.text + "<br>" + text
|
||||
app.socketio.emit(
|
||||
"notify-update",
|
||||
json.dumps(
|
||||
{
|
||||
"id": self.eid,
|
||||
"text": self.text,
|
||||
}
|
||||
),
|
||||
namespace="/sio",
|
||||
room="admin",
|
||||
)
|
||||
sleep(0.001)
|
||||
|
||||
def increment(self,data={'name':'','data':[]}):
|
||||
self.item+=1
|
||||
log.info('INCREMENT '+self.eid+': '+self.text)
|
||||
app.socketio.emit('notify-increment',
|
||||
json.dumps({'id':self.eid,
|
||||
'title':self.title,
|
||||
'text': '['+str(self.item)+'/'+str(self.total)+'] '+self.text+' '+data['name'],
|
||||
'item':self.item,
|
||||
'total':self.total,
|
||||
'table':self.table,
|
||||
'type':self.type,
|
||||
'data':data}),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
def increment(self, data={"name": "", "data": []}):
|
||||
self.item += 1
|
||||
log.info("INCREMENT " + self.eid + ": " + self.text)
|
||||
app.socketio.emit(
|
||||
"notify-increment",
|
||||
json.dumps(
|
||||
{
|
||||
"id": self.eid,
|
||||
"title": self.title,
|
||||
"text": "["
|
||||
+ str(self.item)
|
||||
+ "/"
|
||||
+ str(self.total)
|
||||
+ "] "
|
||||
+ self.text
|
||||
+ " "
|
||||
+ data["name"],
|
||||
"item": self.item,
|
||||
"total": self.total,
|
||||
"table": self.table,
|
||||
"type": self.type,
|
||||
"data": data,
|
||||
}
|
||||
),
|
||||
namespace="/sio",
|
||||
room="admin",
|
||||
)
|
||||
sleep(0.0001)
|
||||
|
||||
def decrement(self,data={'name':'','data':[]}):
|
||||
self.item-=1
|
||||
log.info('DECREMENT '+self.eid+': '+self.text)
|
||||
app.socketio.emit('notify-decrement',
|
||||
json.dumps({'id':self.eid,
|
||||
'title':self.title,
|
||||
'text': '['+str(self.item)+'/'+str(self.total)+'] '+self.text+' '+data['name'],
|
||||
'item':self.item,
|
||||
'total':self.total,
|
||||
'table':self.table,
|
||||
'type':self.type,
|
||||
'data':data}),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
def decrement(self, data={"name": "", "data": []}):
|
||||
self.item -= 1
|
||||
log.info("DECREMENT " + self.eid + ": " + self.text)
|
||||
app.socketio.emit(
|
||||
"notify-decrement",
|
||||
json.dumps(
|
||||
{
|
||||
"id": self.eid,
|
||||
"title": self.title,
|
||||
"text": "["
|
||||
+ str(self.item)
|
||||
+ "/"
|
||||
+ str(self.total)
|
||||
+ "] "
|
||||
+ self.text
|
||||
+ " "
|
||||
+ data["name"],
|
||||
"item": self.item,
|
||||
"total": self.total,
|
||||
"table": self.table,
|
||||
"type": self.type,
|
||||
"data": data,
|
||||
}
|
||||
),
|
||||
namespace="/sio",
|
||||
room="admin",
|
||||
)
|
||||
sleep(0.001)
|
||||
|
||||
def reload(self):
|
||||
app.socketio.emit('reload',
|
||||
json.dumps({}),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
app.socketio.emit("reload", json.dumps({}), namespace="/sio", room="admin")
|
||||
sleep(0.0001)
|
||||
|
||||
def table(self,event,table,data={}):
|
||||
def table(self, event, table, data={}):
|
||||
# refresh, add, delete, update
|
||||
app.socketio.emit('table_'+event,
|
||||
json.dumps({'table':table,'data':data}),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
sleep(0.0001)
|
||||
app.socketio.emit(
|
||||
"table_" + event,
|
||||
json.dumps({"table": table, "data": data}),
|
||||
namespace="/sio",
|
||||
room="admin",
|
||||
)
|
||||
sleep(0.0001)
|
||||
|
|
|
@ -3,5 +3,6 @@
|
|||
class UserExists(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UserNotFound(Exception):
|
||||
pass
|
||||
pass
|
||||
|
|
|
@ -1,55 +1,74 @@
|
|||
import random, string
|
||||
from pprint import pprint
|
||||
import random
|
||||
import string
|
||||
from collections import Counter
|
||||
from pprint import pprint
|
||||
|
||||
|
||||
def system_username(username):
|
||||
return True if username in ['guest','ddadmin','admin'] or username.startswith('system_') else False
|
||||
return (
|
||||
True
|
||||
if username in ["guest", "ddadmin", "admin"] or username.startswith("system_")
|
||||
else False
|
||||
)
|
||||
|
||||
|
||||
def system_group(groupname):
|
||||
return True if groupname in ['admin','manager','teacher','student'] else False
|
||||
return True if groupname in ["admin", "manager", "teacher", "student"] else False
|
||||
|
||||
def get_group_from_group_id(group_id,groups):
|
||||
return next((d for d in groups if d.get('id') == group_id), None)
|
||||
|
||||
def get_gid_from_kgroup_id(kgroup_id,groups):
|
||||
def get_group_from_group_id(group_id, groups):
|
||||
return next((d for d in groups if d.get("id") == group_id), None)
|
||||
|
||||
|
||||
def get_gid_from_kgroup_id(kgroup_id, groups):
|
||||
# print(kgroup_id)
|
||||
# pprint(groups)
|
||||
# return get_group_from_group_id(kgroup_id,groups)['path'].replace('/','.')[1:]
|
||||
return [g['path'].replace('/','.')[1:] for g in groups if g['id'] == kgroup_id][0]
|
||||
return [g["path"].replace("/", ".")[1:] for g in groups if g["id"] == kgroup_id][0]
|
||||
|
||||
|
||||
def get_gids_from_kgroup_ids(kgroup_ids, groups):
|
||||
return [get_gid_from_kgroup_id(kgroup_id, groups) for kgroup_id in kgroup_ids]
|
||||
|
||||
def get_gids_from_kgroup_ids(kgroup_ids,groups):
|
||||
return [get_gid_from_kgroup_id(kgroup_id,groups) for kgroup_id in kgroup_ids]
|
||||
|
||||
def kpath2gid(path):
|
||||
# print(path.replace('/','.')[1:])
|
||||
return path.replace('/','.')[1:]
|
||||
return path.replace("/", ".")[1:]
|
||||
|
||||
|
||||
def gid2kpath(gid):
|
||||
return '/'+gid.replace('.','/')
|
||||
return "/" + gid.replace(".", "/")
|
||||
|
||||
|
||||
def count_repeated(itemslist):
|
||||
print(Counter(itemslist))
|
||||
|
||||
|
||||
def groups_kname2gid(groups):
|
||||
return [name.replace('.','/') for name in groups]
|
||||
return [name.replace(".", "/") for name in groups]
|
||||
|
||||
|
||||
def groups_path2id(groups):
|
||||
return [g.replace('/','.')[1:] for g in groups]
|
||||
return [g.replace("/", ".")[1:] for g in groups]
|
||||
|
||||
|
||||
def groups_id2path(groups):
|
||||
return ['/'+g.replace('.','/') for g in groups]
|
||||
return ["/" + g.replace(".", "/") for g in groups]
|
||||
|
||||
|
||||
def filter_roles_list(role_list):
|
||||
client_roles=['admin','manager','teacher','student']
|
||||
client_roles = ["admin", "manager", "teacher", "student"]
|
||||
return [r for r in role_list if r in client_roles]
|
||||
|
||||
|
||||
def filter_roles_listofdicts(role_listofdicts):
|
||||
client_roles=['admin','manager','teacher','student']
|
||||
return [r for r in role_listofdicts if r['name'] in client_roles]
|
||||
client_roles = ["admin", "manager", "teacher", "student"]
|
||||
return [r for r in role_listofdicts if r["name"] in client_roles]
|
||||
|
||||
|
||||
def rand_password(lenght):
|
||||
characters = string.ascii_letters + string.digits + string.punctuation
|
||||
passwd = ''.join(random.choice(characters) for i in range(lenght))
|
||||
passwd = "".join(random.choice(characters) for i in range(lenght))
|
||||
while not any(ele.isupper() for ele in passwd):
|
||||
passwd = ''.join(random.choice(characters) for i in range(lenght))
|
||||
return passwd
|
||||
passwd = "".join(random.choice(characters) for i in range(lenght))
|
||||
return passwd
|
||||
|
|
|
@ -1,68 +1,82 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import time ,os
|
||||
from admin import app
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
from pprint import pprint
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from keycloak import KeycloakAdmin
|
||||
|
||||
from admin import app
|
||||
|
||||
from .postgres import Postgres
|
||||
|
||||
class KeycloakClient():
|
||||
"""https://www.keycloak.org/docs-api/13.0/rest-api/index.html
|
||||
https://github.com/marcospereirampj/python-keycloak
|
||||
https://gist.github.com/kaqfa/99829941121188d7cef8271f93f52f1f
|
||||
"""
|
||||
def __init__(self,
|
||||
url="http://isard-sso-keycloak:8080/auth/",
|
||||
username=os.environ['KEYCLOAK_USER'],
|
||||
password=os.environ['KEYCLOAK_PASSWORD'],
|
||||
realm='master',
|
||||
verify=True):
|
||||
self.url=url
|
||||
self.username=username
|
||||
self.password=password
|
||||
self.realm=realm
|
||||
self.verify=verify
|
||||
|
||||
self.keycloak_pg=Postgres('isard-apps-postgresql','keycloak',os.environ['KEYCLOAK_DB_USER'],os.environ['KEYCLOAK_DB_PASSWORD'])
|
||||
class KeycloakClient:
|
||||
"""https://www.keycloak.org/docs-api/13.0/rest-api/index.html
|
||||
https://github.com/marcospereirampj/python-keycloak
|
||||
https://gist.github.com/kaqfa/99829941121188d7cef8271f93f52f1f
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url="http://isard-sso-keycloak:8080/auth/",
|
||||
username=os.environ["KEYCLOAK_USER"],
|
||||
password=os.environ["KEYCLOAK_PASSWORD"],
|
||||
realm="master",
|
||||
verify=True,
|
||||
):
|
||||
self.url = url
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.realm = realm
|
||||
self.verify = verify
|
||||
|
||||
self.keycloak_pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"keycloak",
|
||||
os.environ["KEYCLOAK_DB_USER"],
|
||||
os.environ["KEYCLOAK_DB_PASSWORD"],
|
||||
)
|
||||
|
||||
def connect(self):
|
||||
self.keycloak_admin = KeycloakAdmin(server_url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm_name=self.realm,
|
||||
verify=self.verify)
|
||||
# from keycloak import KeycloakAdmin
|
||||
# keycloak_admin = KeycloakAdmin(server_url="http://isard-sso-keycloak:8080/auth/",username="admin",password="keycloakkeycloak",realm_name="master",verify=False)
|
||||
self.keycloak_admin = KeycloakAdmin(
|
||||
server_url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm_name=self.realm,
|
||||
verify=self.verify,
|
||||
)
|
||||
|
||||
######## Example create group and subgroup
|
||||
|
||||
# try:
|
||||
# self.add_group('level1')
|
||||
# except:
|
||||
# self.delete_group(self.get_group('/level1')['id'])
|
||||
# self.add_group('level1')
|
||||
# self.add_group('level2',parent=self.get_group('/level1')['id'])
|
||||
# pprint(self.get_groups())
|
||||
# from keycloak import KeycloakAdmin
|
||||
# keycloak_admin = KeycloakAdmin(server_url="http://isard-sso-keycloak:8080/auth/",username="admin",password="keycloakkeycloak",realm_name="master",verify=False)
|
||||
|
||||
######## Example roles
|
||||
# try:
|
||||
# self.add_role('superman')
|
||||
# except:
|
||||
# self.delete_role('superman')
|
||||
# self.add_role('superman')
|
||||
# pprint(self.get_roles())
|
||||
######## Example create group and subgroup
|
||||
|
||||
''' USERS '''
|
||||
# try:
|
||||
# self.add_group('level1')
|
||||
# except:
|
||||
# self.delete_group(self.get_group('/level1')['id'])
|
||||
# self.add_group('level1')
|
||||
# self.add_group('level2',parent=self.get_group('/level1')['id'])
|
||||
# pprint(self.get_groups())
|
||||
|
||||
def get_user_id(self,username):
|
||||
######## Example roles
|
||||
# try:
|
||||
# self.add_role('superman')
|
||||
# except:
|
||||
# self.delete_role('superman')
|
||||
# self.add_role('superman')
|
||||
# pprint(self.get_roles())
|
||||
|
||||
""" USERS """
|
||||
|
||||
def get_user_id(self, username):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_user_id(username)
|
||||
|
||||
|
@ -85,24 +99,31 @@ class KeycloakClient():
|
|||
group by u.id,u.username,u.email,u.first_name,u.last_name, u.realm_id, u.enabled, ua.value
|
||||
order by u.username"""
|
||||
|
||||
(headers,users)=self.keycloak_pg.select_with_headers(q)
|
||||
(headers, users) = self.keycloak_pg.select_with_headers(q)
|
||||
|
||||
users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-3]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-2]))]) +\
|
||||
([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users]
|
||||
|
||||
users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-3]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-2]))]) +\
|
||||
([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users_with_lists]
|
||||
users_with_lists = [
|
||||
list(l[:-4])
|
||||
+ ([[]] if l[-4] == [None] else [list(set(l[-4]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-3]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users
|
||||
]
|
||||
|
||||
users_with_lists = [
|
||||
list(l[:-4])
|
||||
+ ([[]] if l[-4] == [None] else [list(set(l[-4]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-3]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users_with_lists
|
||||
]
|
||||
|
||||
list_dict_users = [dict(zip(headers, r)) for r in users_with_lists]
|
||||
|
||||
# self.connect()
|
||||
# groups = self.keycloak_admin.get_groups()
|
||||
|
||||
|
||||
# for user in list_dict_users:
|
||||
# new_user_groups = []
|
||||
# for group_id in user['group']:
|
||||
|
@ -113,31 +134,31 @@ class KeycloakClient():
|
|||
# user['group']=new_user_groups
|
||||
return list_dict_users
|
||||
|
||||
|
||||
|
||||
def getparent(self,group_id, data):
|
||||
def getparent(self, group_id, data):
|
||||
# Recursively get full path from any group_id in the tree
|
||||
path = ""
|
||||
for item in data:
|
||||
if group_id == item[0]:
|
||||
path = self.getparent(item[2], data)
|
||||
path = f'{path}/{item[1]}'
|
||||
path = f"{path}/{item[1]}"
|
||||
return path
|
||||
|
||||
def get_group_path(self,group_id):
|
||||
def get_group_path(self, group_id):
|
||||
# Get full path using getparent recursive func
|
||||
# RETURNS: String with full path
|
||||
q = """SELECT * FROM keycloak_group"""
|
||||
groups=self.keycloak_pg.select(q)
|
||||
return self.getparent(group_id,groups)
|
||||
groups = self.keycloak_pg.select(q)
|
||||
return self.getparent(group_id, groups)
|
||||
|
||||
def get_user_groups_paths(self,user_id):
|
||||
def get_user_groups_paths(self, user_id):
|
||||
# Get full paths for user grups
|
||||
# RETURNS list of paths
|
||||
q = """SELECT group_id FROM user_group_membership WHERE user_id = '%s'""" % (user_id)
|
||||
user_group_ids=self.keycloak_pg.select(q)
|
||||
q = """SELECT group_id FROM user_group_membership WHERE user_id = '%s'""" % (
|
||||
user_id
|
||||
)
|
||||
user_group_ids = self.keycloak_pg.select(q)
|
||||
|
||||
paths=[]
|
||||
paths = []
|
||||
for g in user_group_ids:
|
||||
paths.append(self.get_group_path(g[0]))
|
||||
return paths
|
||||
|
@ -151,90 +172,116 @@ class KeycloakClient():
|
|||
# user['roles']= [r['name'] for r in self.keycloak_admin.get_realm_roles_of_user(user_id=user['id'])]
|
||||
# return users
|
||||
|
||||
def add_user(self,username,first,last,email,password,group=False,temporary=True,enabled=True):
|
||||
def add_user(
|
||||
self,
|
||||
username,
|
||||
first,
|
||||
last,
|
||||
email,
|
||||
password,
|
||||
group=False,
|
||||
temporary=True,
|
||||
enabled=True,
|
||||
):
|
||||
# RETURNS string with keycloak user id (the main id in this app)
|
||||
self.connect()
|
||||
username=username.lower()
|
||||
username = username.lower()
|
||||
try:
|
||||
uid=self.keycloak_admin.create_user({"email": email,
|
||||
"username": username,
|
||||
"enabled": enabled,
|
||||
"firstName": first,
|
||||
"lastName": last,
|
||||
"credentials":[{"type":"password",
|
||||
"value":password,
|
||||
"temporary":temporary}]})
|
||||
uid = self.keycloak_admin.create_user(
|
||||
{
|
||||
"email": email,
|
||||
"username": username,
|
||||
"enabled": enabled,
|
||||
"firstName": first,
|
||||
"lastName": last,
|
||||
"credentials": [
|
||||
{"type": "password", "value": password, "temporary": temporary}
|
||||
],
|
||||
}
|
||||
)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
|
||||
if group:
|
||||
path = '/'+group if group[1:] != '/' else group
|
||||
path = "/" + group if group[1:] != "/" else group
|
||||
try:
|
||||
gid=self.keycloak_admin.get_group_by_path(path=path,search_in_subgroups=False)['id']
|
||||
gid = self.keycloak_admin.get_group_by_path(
|
||||
path=path, search_in_subgroups=False
|
||||
)["id"]
|
||||
except:
|
||||
self.keycloak_admin.create_group({"name":group})
|
||||
gid=self.keycloak_admin.get_group_by_path(path)['id']
|
||||
self.keycloak_admin.group_user_add(uid,gid)
|
||||
self.keycloak_admin.create_group({"name": group})
|
||||
gid = self.keycloak_admin.get_group_by_path(path)["id"]
|
||||
self.keycloak_admin.group_user_add(uid, gid)
|
||||
return uid
|
||||
|
||||
def update_user_pwd(self,user_id,password,temporary=True):
|
||||
# Updates
|
||||
payload={"credentials":[{"type":"password",
|
||||
"value":password,
|
||||
"temporary":temporary}]}
|
||||
def update_user_pwd(self, user_id, password, temporary=True):
|
||||
# Updates
|
||||
payload = {
|
||||
"credentials": [
|
||||
{"type": "password", "value": password, "temporary": temporary}
|
||||
]
|
||||
}
|
||||
self.connect()
|
||||
return self.keycloak_admin.update_user( user_id, payload)
|
||||
return self.keycloak_admin.update_user(user_id, payload)
|
||||
|
||||
def user_update(self,user_id,enabled,email,first,last,groups=[],roles=[]):
|
||||
def user_update(self, user_id, enabled, email, first, last, groups=[], roles=[]):
|
||||
## NOTE: Roles didn't seem to be updated/added. Also not confident with groups
|
||||
# Updates
|
||||
payload={"enabled":enabled,
|
||||
"email":email,
|
||||
"firstName":first,
|
||||
"lastName":last,
|
||||
"groups":groups,
|
||||
"realmRoles":roles}
|
||||
# Updates
|
||||
payload = {
|
||||
"enabled": enabled,
|
||||
"email": email,
|
||||
"firstName": first,
|
||||
"lastName": last,
|
||||
"groups": groups,
|
||||
"realmRoles": roles,
|
||||
}
|
||||
self.connect()
|
||||
return self.keycloak_admin.update_user( user_id, payload)
|
||||
return self.keycloak_admin.update_user(user_id, payload)
|
||||
|
||||
def user_enable(self,user_id):
|
||||
payload={"enabled":True}
|
||||
def user_enable(self, user_id):
|
||||
payload = {"enabled": True}
|
||||
self.connect()
|
||||
return self.keycloak_admin.update_user( user_id, payload)
|
||||
return self.keycloak_admin.update_user(user_id, payload)
|
||||
|
||||
def user_disable(self,user_id):
|
||||
payload={"enabled":False}
|
||||
def user_disable(self, user_id):
|
||||
payload = {"enabled": False}
|
||||
self.connect()
|
||||
return self.keycloak_admin.update_user( user_id, payload)
|
||||
return self.keycloak_admin.update_user(user_id, payload)
|
||||
|
||||
def group_user_remove(self,user_id,group_id):
|
||||
def group_user_remove(self, user_id, group_id):
|
||||
self.connect()
|
||||
return self.keycloak_admin.group_user_remove(user_id,group_id)
|
||||
return self.keycloak_admin.group_user_remove(user_id, group_id)
|
||||
|
||||
# def add_user_role(self,user_id,role_id):
|
||||
# self.connect()
|
||||
# return self.keycloak_admin.assign_role(client_id=client_id, user_id=user_id, role_id=role_id, role_name="test")
|
||||
|
||||
def remove_user_realm_roles(self,user_id,roles):
|
||||
def remove_user_realm_roles(self, user_id, roles):
|
||||
self.connect()
|
||||
roles = [r for r in self.get_user_realm_roles(user_id) if r['name'] in ['admin','manager','teacher','student']]
|
||||
return self.keycloak_admin.delete_realm_roles_of_user(user_id,roles)
|
||||
roles = [
|
||||
r
|
||||
for r in self.get_user_realm_roles(user_id)
|
||||
if r["name"] in ["admin", "manager", "teacher", "student"]
|
||||
]
|
||||
return self.keycloak_admin.delete_realm_roles_of_user(user_id, roles)
|
||||
|
||||
def delete_user(self,userid):
|
||||
def delete_user(self, userid):
|
||||
self.connect()
|
||||
return self.keycloak_admin.delete_user(user_id=userid)
|
||||
|
||||
def get_user_groups(self,userid):
|
||||
def get_user_groups(self, userid):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_user_groups(user_id=userid)
|
||||
|
||||
def get_user_realm_roles(self,userid):
|
||||
def get_user_realm_roles(self, userid):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_realm_roles_of_user(user_id=userid)
|
||||
|
||||
def add_user_client_role(self,client_id,user_id,role_id,role_name):
|
||||
def add_user_client_role(self, client_id, user_id, role_id, role_name):
|
||||
self.connect()
|
||||
return self.keycloak_admin.assign_client_role(client_id=client_id, user_id=user_id, role_id=role_id, role_name="test")
|
||||
return self.keycloak_admin.assign_client_role(
|
||||
client_id=client_id, user_id=user_id, role_id=role_id, role_name="test"
|
||||
)
|
||||
|
||||
## GROUPS
|
||||
def get_all_groups(self):
|
||||
|
@ -242,75 +289,77 @@ class KeycloakClient():
|
|||
self.connect()
|
||||
return self.keycloak_admin.get_groups()
|
||||
|
||||
def get_recursive_groups(self, l_groups,l=[]):
|
||||
def get_recursive_groups(self, l_groups, l=[]):
|
||||
for d_group in l_groups:
|
||||
d = {}
|
||||
for key, value in d_group.items():
|
||||
if key == 'subGroups':
|
||||
self.get_recursive_groups(value,l)
|
||||
if key == "subGroups":
|
||||
self.get_recursive_groups(value, l)
|
||||
else:
|
||||
d[key]=value
|
||||
d[key] = value
|
||||
l.append(d)
|
||||
return l
|
||||
|
||||
def get_groups(self,with_subgroups=True):
|
||||
def get_groups(self, with_subgroups=True):
|
||||
## RETURNS ALL GROUPS in root list
|
||||
self.connect()
|
||||
groups = self.keycloak_admin.get_groups()
|
||||
return self.get_recursive_groups(groups)
|
||||
subgroups=[]
|
||||
subgroups1=[]
|
||||
subgroups = []
|
||||
subgroups1 = []
|
||||
# This needs to be recursive function
|
||||
if with_subgroups:
|
||||
for group in groups:
|
||||
if len(group['subGroups']):
|
||||
for sg in group['subGroups']:
|
||||
if len(group["subGroups"]):
|
||||
for sg in group["subGroups"]:
|
||||
subgroups.append(sg)
|
||||
# for sgroup in subgroups:
|
||||
# if len(sgroup['subGroups']):
|
||||
# for sg1 in sgroup['subGroups']:
|
||||
# subgroups1.append(sg1)
|
||||
|
||||
return groups+subgroups+subgroups1
|
||||
|
||||
def get_group_by_id(self,group_id):
|
||||
return groups + subgroups + subgroups1
|
||||
|
||||
def get_group_by_id(self, group_id):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_group(group_id=group_id)
|
||||
|
||||
def get_group_by_path(self,path,recursive=True):
|
||||
def get_group_by_path(self, path, recursive=True):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_group_by_path(path=path,search_in_subgroups=recursive)
|
||||
return self.keycloak_admin.get_group_by_path(
|
||||
path=path, search_in_subgroups=recursive
|
||||
)
|
||||
|
||||
def add_group(self,name,parent=None,skip_exists=False):
|
||||
def add_group(self, name, parent=None, skip_exists=False):
|
||||
self.connect()
|
||||
if parent != None:
|
||||
parent=self.get_group_by_path(parent)['id']
|
||||
return self.keycloak_admin.create_group({"name":name}, parent=parent)
|
||||
if parent != None:
|
||||
parent = self.get_group_by_path(parent)["id"]
|
||||
return self.keycloak_admin.create_group({"name": name}, parent=parent)
|
||||
|
||||
def delete_group(self,group_id):
|
||||
def delete_group(self, group_id):
|
||||
self.connect()
|
||||
return self.keycloak_admin.delete_group(group_id=group_id)
|
||||
|
||||
def group_user_add(self,user_id,group_id):
|
||||
def group_user_add(self, user_id, group_id):
|
||||
self.connect()
|
||||
return self.keycloak_admin.group_user_add(user_id, group_id)
|
||||
|
||||
def add_group_tree(self,path):
|
||||
parts=path.split('/')
|
||||
parent_path='/'
|
||||
for i in range(1,len(parts)):
|
||||
def add_group_tree(self, path):
|
||||
parts = path.split("/")
|
||||
parent_path = "/"
|
||||
for i in range(1, len(parts)):
|
||||
if i == 1:
|
||||
try:
|
||||
self.add_group(parts[i],None,skip_exists=True)
|
||||
self.add_group(parts[i], None, skip_exists=True)
|
||||
except:
|
||||
log.warning('KEYCLOAK: Group :'+parts[i]+ ' already exists.')
|
||||
parent_path=parent_path+parts[i]
|
||||
log.warning("KEYCLOAK: Group :" + parts[i] + " already exists.")
|
||||
parent_path = parent_path + parts[i]
|
||||
else:
|
||||
try:
|
||||
self.add_group(parts[i],parent_path,skip_exists=True)
|
||||
self.add_group(parts[i], parent_path, skip_exists=True)
|
||||
except:
|
||||
log.warning('KEYCLOAK: Group :'+parts[i]+ ' already exists.')
|
||||
parent_path=parent_path+parts[i]
|
||||
log.warning("KEYCLOAK: Group :" + parts[i] + " already exists.")
|
||||
parent_path = parent_path + parts[i]
|
||||
|
||||
# parts=path.split('/')
|
||||
# parent_path=None
|
||||
|
@ -319,126 +368,149 @@ class KeycloakClient():
|
|||
# try:
|
||||
# self.add_group(parts[i],parent_path,skip_exists=True)
|
||||
# except:
|
||||
# if parent_path==None:
|
||||
# if parent_path==None:
|
||||
# parent_path='/'+parts[i]
|
||||
# else:
|
||||
# parent_path=self.get_group_by_path(parent_path)['path']
|
||||
# parent_path=parent_path+'/'+parts[i]
|
||||
# continue
|
||||
|
||||
# if parent_path==None:
|
||||
|
||||
# if parent_path==None:
|
||||
# parent_path='/'+parts[i]
|
||||
# else:
|
||||
# parent_path=parent_path+'/'+parts[i]
|
||||
|
||||
# try:
|
||||
# if i == 1: parent_id=self.add_group(parts[i])
|
||||
# except:
|
||||
# # Main already exists?? What a fail!
|
||||
# parent_id=self.get_group(parent_id)['id']
|
||||
# continue
|
||||
# self.add_group(parts[i],parent_id)
|
||||
|
||||
def add_user_with_groups_and_role(self,username,first,last,email,password,role,groups):
|
||||
# try:
|
||||
# if i == 1: parent_id=self.add_group(parts[i])
|
||||
# except:
|
||||
# # Main already exists?? What a fail!
|
||||
# parent_id=self.get_group(parent_id)['id']
|
||||
# continue
|
||||
# self.add_group(parts[i],parent_id)
|
||||
|
||||
def add_user_with_groups_and_role(
|
||||
self, username, first, last, email, password, role, groups
|
||||
):
|
||||
## Add user
|
||||
uid=self.add_user(username,first,last,email,password)
|
||||
uid = self.add_user(username, first, last, email, password)
|
||||
## Add user to role
|
||||
log.info('User uid: '+str(uid)+ ' role: '+str(role))
|
||||
log.info("User uid: " + str(uid) + " role: " + str(role))
|
||||
try:
|
||||
therole=role[0]
|
||||
therole = role[0]
|
||||
except:
|
||||
therole=''
|
||||
log.info(self.assign_realm_roles(uid,role))
|
||||
therole = ""
|
||||
log.info(self.assign_realm_roles(uid, role))
|
||||
## Create groups in user
|
||||
for g in groups:
|
||||
log.warning('Creating keycloak group: '+g)
|
||||
parts=g.split('/')
|
||||
parent_path=None
|
||||
for i in range(1,len(parts)):
|
||||
log.warning("Creating keycloak group: " + g)
|
||||
parts = g.split("/")
|
||||
parent_path = None
|
||||
for i in range(1, len(parts)):
|
||||
# parent_id=None if parent_path==None else self.get_group(parent_path)['id']
|
||||
try:
|
||||
self.add_group(parts[i],parent_path,skip_exists=True)
|
||||
self.add_group(parts[i], parent_path, skip_exists=True)
|
||||
except:
|
||||
log.warning('Group '+str(parent_path)+ ' already exists. Skipping creation')
|
||||
log.warning(
|
||||
"Group "
|
||||
+ str(parent_path)
|
||||
+ " already exists. Skipping creation"
|
||||
)
|
||||
pass
|
||||
if parent_path is None:
|
||||
thepath='/'+parts[i]
|
||||
thepath = "/" + parts[i]
|
||||
else:
|
||||
thepath=parent_path+'/'+parts[i]
|
||||
if thepath=='/':
|
||||
log.warning('Not adding the user '+username+' to any group as does not have any...')
|
||||
thepath = parent_path + "/" + parts[i]
|
||||
if thepath == "/":
|
||||
log.warning(
|
||||
"Not adding the user "
|
||||
+ username
|
||||
+ " to any group as does not have any..."
|
||||
)
|
||||
continue
|
||||
gid=self.get_group_by_path(path=thepath)['id']
|
||||
gid = self.get_group_by_path(path=thepath)["id"]
|
||||
|
||||
log.warning('Adding '+username+' with uuid: '+uid+' to group '+g+' with uuid: '+gid)
|
||||
self.keycloak_admin.group_user_add(uid,gid)
|
||||
log.warning(
|
||||
"Adding "
|
||||
+ username
|
||||
+ " with uuid: "
|
||||
+ uid
|
||||
+ " to group "
|
||||
+ g
|
||||
+ " with uuid: "
|
||||
+ gid
|
||||
)
|
||||
self.keycloak_admin.group_user_add(uid, gid)
|
||||
|
||||
|
||||
if parent_path==None: parent_path=''
|
||||
parent_path=parent_path+'/'+parts[i]
|
||||
if parent_path == None:
|
||||
parent_path = ""
|
||||
parent_path = parent_path + "/" + parts[i]
|
||||
|
||||
|
||||
# self.group_user_add(uid,gid)
|
||||
|
||||
|
||||
## ROLES
|
||||
def get_roles(self):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_realm_roles()
|
||||
|
||||
def get_role(self,name):
|
||||
def get_role(self, name):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_realm_role(name)
|
||||
|
||||
def add_role(self,name,description=''):
|
||||
def add_role(self, name, description=""):
|
||||
self.connect()
|
||||
return self.keycloak_admin.create_realm_role({"name":name, "description":description})
|
||||
return self.keycloak_admin.create_realm_role(
|
||||
{"name": name, "description": description}
|
||||
)
|
||||
|
||||
def delete_role(self,name):
|
||||
def delete_role(self, name):
|
||||
self.connect()
|
||||
return self.keycloak_admin.delete_realm_role(name)
|
||||
|
||||
|
||||
## CLIENTS
|
||||
|
||||
def get_client_roles(self,client_id):
|
||||
def get_client_roles(self, client_id):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_client_roles(client_id=client_id)
|
||||
|
||||
def add_client_role(self,client_id,name,description=''):
|
||||
def add_client_role(self, client_id, name, description=""):
|
||||
self.connect()
|
||||
return self.keycloak_admin.create_client_role(client_id, {'name': name, 'description':description, 'clientRole': True})
|
||||
|
||||
return self.keycloak_admin.create_client_role(
|
||||
client_id, {"name": name, "description": description, "clientRole": True}
|
||||
)
|
||||
|
||||
## SYSTEM
|
||||
def get_server_info(self):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_server_info()
|
||||
|
||||
|
||||
def get_server_clients(self):
|
||||
self.connect()
|
||||
return self.keycloak_admin.get_clients()
|
||||
|
||||
def get_server_rsa_key(self):
|
||||
self.connect()
|
||||
rsa_key = [k for k in self.keycloak_admin.get_keys()['keys'] if k['type']=='RSA'][0]
|
||||
return {'name':rsa_key['kid'],'certificate':rsa_key['certificate']}
|
||||
rsa_key = [
|
||||
k for k in self.keycloak_admin.get_keys()["keys"] if k["type"] == "RSA"
|
||||
][0]
|
||||
return {"name": rsa_key["kid"], "certificate": rsa_key["certificate"]}
|
||||
|
||||
## REALM
|
||||
def assign_realm_roles(self, user_id, role):
|
||||
self.connect()
|
||||
try:
|
||||
role=[r for r in self.keycloak_admin.get_realm_roles() if r['name']==role]
|
||||
role = [
|
||||
r for r in self.keycloak_admin.get_realm_roles() if r["name"] == role
|
||||
]
|
||||
except:
|
||||
return False
|
||||
return self.keycloak_admin.assign_realm_roles(user_id=user_id, roles=role)
|
||||
# return self.keycloak_admin.assign_realm_roles(user_id=user_id, client_id=None, roles=role)
|
||||
|
||||
## CLIENTS
|
||||
def delete_client(self,clientid):
|
||||
def delete_client(self, clientid):
|
||||
self.connect()
|
||||
return self.keycloak_admin.delete_client(clientid)
|
||||
|
||||
def add_client(self,client):
|
||||
def add_client(self, client):
|
||||
self.connect()
|
||||
return self.keycloak_admin.create_client(client)
|
||||
|
|
|
@ -1,24 +1,39 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
|
||||
from admin import app
|
||||
|
||||
import os, sys
|
||||
import logging as log
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
class loadConfig():
|
||||
from admin import app
|
||||
|
||||
def __init__(self, app=None):
|
||||
|
||||
class loadConfig:
|
||||
def __init__(self, app=None):
|
||||
try:
|
||||
app.config.setdefault('DOMAIN', os.environ['DOMAIN'])
|
||||
app.config.setdefault('KEYCLOAK_POSTGRES_USER', os.environ['KEYCLOAK_DB_USER'])
|
||||
app.config.setdefault('KEYCLOAK_POSTGRES_PASSWORD', os.environ['KEYCLOAK_DB_PASSWORD'])
|
||||
app.config.setdefault('MOODLE_POSTGRES_USER', os.environ['MOODLE_POSTGRES_USER'])
|
||||
app.config.setdefault('MOODLE_POSTGRES_PASSWORD', os.environ['MOODLE_POSTGRES_PASSWORD'])
|
||||
app.config.setdefault('NEXTCLOUD_POSTGRES_USER', os.environ['NEXTCLOUD_POSTGRES_USER'])
|
||||
app.config.setdefault('NEXTCLOUD_POSTGRES_PASSWORD', os.environ['NEXTCLOUD_POSTGRES_PASSWORD'])
|
||||
app.config.setdefault('VERIFY', True if os.environ['VERIFY']=="true" else False)
|
||||
app.config.setdefault("DOMAIN", os.environ["DOMAIN"])
|
||||
app.config.setdefault(
|
||||
"KEYCLOAK_POSTGRES_USER", os.environ["KEYCLOAK_DB_USER"]
|
||||
)
|
||||
app.config.setdefault(
|
||||
"KEYCLOAK_POSTGRES_PASSWORD", os.environ["KEYCLOAK_DB_PASSWORD"]
|
||||
)
|
||||
app.config.setdefault(
|
||||
"MOODLE_POSTGRES_USER", os.environ["MOODLE_POSTGRES_USER"]
|
||||
)
|
||||
app.config.setdefault(
|
||||
"MOODLE_POSTGRES_PASSWORD", os.environ["MOODLE_POSTGRES_PASSWORD"]
|
||||
)
|
||||
app.config.setdefault(
|
||||
"NEXTCLOUD_POSTGRES_USER", os.environ["NEXTCLOUD_POSTGRES_USER"]
|
||||
)
|
||||
app.config.setdefault(
|
||||
"NEXTCLOUD_POSTGRES_PASSWORD", os.environ["NEXTCLOUD_POSTGRES_PASSWORD"]
|
||||
)
|
||||
app.config.setdefault(
|
||||
"VERIFY", True if os.environ["VERIFY"] == "true" else False
|
||||
)
|
||||
except Exception as e:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
|
|
|
@ -1,35 +1,43 @@
|
|||
import logging as log
|
||||
import traceback
|
||||
from pprint import pprint
|
||||
|
||||
from requests import get, post
|
||||
|
||||
from admin import app
|
||||
|
||||
import logging as log
|
||||
from pprint import pprint
|
||||
import traceback
|
||||
|
||||
from .postgres import Postgres
|
||||
from .exceptions import UserExists, UserNotFound
|
||||
from .postgres import Postgres
|
||||
|
||||
# Module variables to connect to moodle api
|
||||
|
||||
class Moodle():
|
||||
|
||||
class Moodle:
|
||||
"""https://github.com/mrcinv/moodle_api.py
|
||||
https://docs.moodle.org/dev/Web_service_API_functions
|
||||
https://docs.moodle.org/311/en/Using_web_services
|
||||
https://docs.moodle.org/dev/Web_service_API_functions
|
||||
https://docs.moodle.org/311/en/Using_web_services
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
key=app.config["MOODLE_WS_TOKEN"],
|
||||
url="https://moodle."+app.config["DOMAIN"],
|
||||
endpoint="/webservice/rest/server.php",
|
||||
verify=app.config["VERIFY"]):
|
||||
def __init__(
|
||||
self,
|
||||
key=app.config["MOODLE_WS_TOKEN"],
|
||||
url="https://moodle." + app.config["DOMAIN"],
|
||||
endpoint="/webservice/rest/server.php",
|
||||
verify=app.config["VERIFY"],
|
||||
):
|
||||
self.key = key
|
||||
self.url = url
|
||||
self.endpoint = endpoint
|
||||
self.verify=verify
|
||||
self.verify = verify
|
||||
|
||||
self.moodle_pg=Postgres('isard-apps-postgresql','moodle',app.config['MOODLE_POSTGRES_USER'],app.config['MOODLE_POSTGRES_PASSWORD'])
|
||||
self.moodle_pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"moodle",
|
||||
app.config["MOODLE_POSTGRES_USER"],
|
||||
app.config["MOODLE_POSTGRES_PASSWORD"],
|
||||
)
|
||||
|
||||
|
||||
def rest_api_parameters(self, in_args, prefix='', out_dict=None):
|
||||
def rest_api_parameters(self, in_args, prefix="", out_dict=None):
|
||||
"""Transform dictionary/array structure to a flat dictionary, with key names
|
||||
defining the structure.
|
||||
Example usage:
|
||||
|
@ -37,23 +45,23 @@ class Moodle():
|
|||
{'courses[0][id]':1,
|
||||
'courses[0][name]':'course1'}
|
||||
"""
|
||||
if out_dict==None:
|
||||
if out_dict == None:
|
||||
out_dict = {}
|
||||
if not type(in_args) in (list,dict):
|
||||
if not type(in_args) in (list, dict):
|
||||
out_dict[prefix] = in_args
|
||||
return out_dict
|
||||
if prefix == '':
|
||||
prefix = prefix + '{0}'
|
||||
if prefix == "":
|
||||
prefix = prefix + "{0}"
|
||||
else:
|
||||
prefix = prefix + '[{0}]'
|
||||
if type(in_args)==list:
|
||||
prefix = prefix + "[{0}]"
|
||||
if type(in_args) == list:
|
||||
for idx, item in enumerate(in_args):
|
||||
self.rest_api_parameters(item, prefix.format(idx), out_dict)
|
||||
elif type(in_args)==dict:
|
||||
elif type(in_args) == dict:
|
||||
for key, item in in_args.items():
|
||||
self.rest_api_parameters(item, prefix.format(key), out_dict)
|
||||
return out_dict
|
||||
|
||||
|
||||
def call(self, fname, **kwargs):
|
||||
"""Calls moodle API function with function name fname and keyword arguments.
|
||||
Example:
|
||||
|
@ -61,54 +69,69 @@ class Moodle():
|
|||
courses = [{'id': 1, 'fullname': 'My favorite course'}])
|
||||
"""
|
||||
parameters = self.rest_api_parameters(kwargs)
|
||||
parameters.update({"wstoken": self.key, 'moodlewsrestformat': 'json', "wsfunction": fname})
|
||||
response = post(self.url+self.endpoint, parameters, verify=self.verify)
|
||||
parameters.update(
|
||||
{"wstoken": self.key, "moodlewsrestformat": "json", "wsfunction": fname}
|
||||
)
|
||||
response = post(self.url + self.endpoint, parameters, verify=self.verify)
|
||||
response = response.json()
|
||||
if type(response) == dict and response.get('exception'):
|
||||
if type(response) == dict and response.get("exception"):
|
||||
raise SystemError(response)
|
||||
return response
|
||||
|
||||
def create_user(self, email, username, password, first_name='-', last_name='-'):
|
||||
if len(self.get_user_by('username',username)['users']):
|
||||
def create_user(self, email, username, password, first_name="-", last_name="-"):
|
||||
if len(self.get_user_by("username", username)["users"]):
|
||||
raise UserExists
|
||||
try:
|
||||
data = [{'username': username, 'email':email,
|
||||
'password': password, 'firstname':first_name, 'lastname':last_name}]
|
||||
user = self.call('core_user_create_users', users=data)
|
||||
return user #[{'id': 8, 'username': 'asdfw'}]
|
||||
data = [
|
||||
{
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": password,
|
||||
"firstname": first_name,
|
||||
"lastname": last_name,
|
||||
}
|
||||
]
|
||||
user = self.call("core_user_create_users", users=data)
|
||||
return user # [{'id': 8, 'username': 'asdfw'}]
|
||||
except SystemError as se:
|
||||
raise SystemError(se.args[0]['message'])
|
||||
raise SystemError(se.args[0]["message"])
|
||||
|
||||
def update_user(self, username, email, first_name, last_name, enabled=True):
|
||||
user = self.get_user_by('username',username)['users'][0]
|
||||
if not len(user):
|
||||
user = self.get_user_by("username", username)["users"][0]
|
||||
if not len(user):
|
||||
raise UserNotFound
|
||||
try:
|
||||
data = [{'id':user['id'],'username': username, 'email':email,
|
||||
'firstname':first_name, 'lastname':last_name,
|
||||
'suspended':0 if enabled else 1}]
|
||||
user = self.call('core_user_update_users', users=data)
|
||||
data = [
|
||||
{
|
||||
"id": user["id"],
|
||||
"username": username,
|
||||
"email": email,
|
||||
"firstname": first_name,
|
||||
"lastname": last_name,
|
||||
"suspended": 0 if enabled else 1,
|
||||
}
|
||||
]
|
||||
user = self.call("core_user_update_users", users=data)
|
||||
return user
|
||||
except SystemError as se:
|
||||
raise SystemError(se.args[0]['message'])
|
||||
raise SystemError(se.args[0]["message"])
|
||||
|
||||
def delete_user(self, user_id):
|
||||
user = self.call('core_user_delete_users', userids=[user_id])
|
||||
user = self.call("core_user_delete_users", userids=[user_id])
|
||||
return user
|
||||
|
||||
def delete_users(self, userids):
|
||||
user = self.call('core_user_delete_users', userids=userids)
|
||||
user = self.call("core_user_delete_users", userids=userids)
|
||||
return user
|
||||
|
||||
def get_user_by(self, key, value):
|
||||
criteria = [{'key': key, 'value': value}]
|
||||
criteria = [{"key": key, "value": value}]
|
||||
try:
|
||||
user = self.call('core_user_get_users', criteria=criteria)
|
||||
user = self.call("core_user_get_users", criteria=criteria)
|
||||
except:
|
||||
raise SystemError("Error calling Moodle API\n", traceback.format_exc())
|
||||
return user
|
||||
#{'users': [{'id': 8, 'username': 'asdfw', 'firstname': 'afowie', 'lastname': 'aokjdnfwe', 'fullname': 'afowie aokjdnfwe', 'email': 'awfewe@ads.com', 'department': '', 'firstaccess': 0, 'lastaccess': 0, 'auth': 'manual', 'suspended': False, 'confirmed': True, 'lang': 'ca', 'theme': '', 'timezone': '99', 'mailformat': 1, 'profileimageurlsmall': 'https://moodle.mydomain.duckdns.org/theme/image.php/cbe/core/1630941606/u/f2', 'profileimageurl': 'https://DOMAIN/theme/image.php/cbe/core/1630941606/u/f1'}], 'warnings': []}
|
||||
|
||||
# {'users': [{'id': 8, 'username': 'asdfw', 'firstname': 'afowie', 'lastname': 'aokjdnfwe', 'fullname': 'afowie aokjdnfwe', 'email': 'awfewe@ads.com', 'department': '', 'firstaccess': 0, 'lastaccess': 0, 'auth': 'manual', 'suspended': False, 'confirmed': True, 'lang': 'ca', 'theme': '', 'timezone': '99', 'mailformat': 1, 'profileimageurlsmall': 'https://moodle.mydomain.duckdns.org/theme/image.php/cbe/core/1630941606/u/f2', 'profileimageurl': 'https://DOMAIN/theme/image.php/cbe/core/1630941606/u/f1'}], 'warnings': []}
|
||||
|
||||
def get_users_with_groups_and_roles(self):
|
||||
q = """select u.id as id, username, firstname as first, lastname as last, email, json_agg(h.name) as groups, json_agg(r.shortname) as roles
|
||||
|
@ -119,8 +142,13 @@ class Moodle():
|
|||
left join mdl_role as r on r.id = ra.roleid
|
||||
where u.deleted = 0
|
||||
group by u.id , username, first, last, email"""
|
||||
(headers,users)=self.moodle_pg.select_with_headers(q)
|
||||
users_with_lists = [list(l[:-2])+([[]] if l[-2] == [None] else [list(set(l[-2]))]) + ([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users]
|
||||
(headers, users) = self.moodle_pg.select_with_headers(q)
|
||||
users_with_lists = [
|
||||
list(l[:-2])
|
||||
+ ([[]] if l[-2] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users
|
||||
]
|
||||
list_dict_users = [dict(zip(headers, r)) for r in users_with_lists]
|
||||
return list_dict_users
|
||||
|
||||
|
@ -134,26 +162,32 @@ class Moodle():
|
|||
|
||||
def enroll_user_to_course(self, user_id, course_id, role_id=5):
|
||||
# 5 is student
|
||||
data = [{'roleid': role_id, 'userid': user_id, 'courseid': course_id}]
|
||||
enrolment = self.call('enrol_manual_enrol_users', enrolments=data)
|
||||
data = [{"roleid": role_id, "userid": user_id, "courseid": course_id}]
|
||||
enrolment = self.call("enrol_manual_enrol_users", enrolments=data)
|
||||
return enrolment
|
||||
|
||||
|
||||
def get_quiz_attempt(self, quiz_id, user_id):
|
||||
attempts = self.call('mod_quiz_get_user_attempts', quizid=quiz_id, userid=user_id)
|
||||
attempts = self.call(
|
||||
"mod_quiz_get_user_attempts", quizid=quiz_id, userid=user_id
|
||||
)
|
||||
return attempts
|
||||
|
||||
|
||||
def get_cohorts(self):
|
||||
cohorts = self.call('core_cohort_get_cohorts')
|
||||
cohorts = self.call("core_cohort_get_cohorts")
|
||||
return cohorts
|
||||
|
||||
def add_system_cohort(self,name,description='',visible=True):
|
||||
visible=1 if visible else 0
|
||||
data = [{'categorytype': {'type': 'system', 'value': ''},
|
||||
'name': name,
|
||||
'idnumber': name,
|
||||
'description': description,
|
||||
'visible': visible}]
|
||||
cohort = self.call('core_cohort_create_cohorts', cohorts=data)
|
||||
def add_system_cohort(self, name, description="", visible=True):
|
||||
visible = 1 if visible else 0
|
||||
data = [
|
||||
{
|
||||
"categorytype": {"type": "system", "value": ""},
|
||||
"name": name,
|
||||
"idnumber": name,
|
||||
"description": description,
|
||||
"visible": visible,
|
||||
}
|
||||
]
|
||||
cohort = self.call("core_cohort_create_cohorts", cohorts=data)
|
||||
return cohort
|
||||
|
||||
# def add_users_to_cohort(self,users,cohort):
|
||||
|
@ -161,52 +195,61 @@ class Moodle():
|
|||
# user = self.call('core_cohort_add_cohort_members', criteria=criteria)
|
||||
# return user
|
||||
|
||||
def add_user_to_cohort(self,userid,cohortid):
|
||||
members=[{'cohorttype':{'type':'id','value':cohortid},
|
||||
'usertype':{'type':'id','value':userid}}]
|
||||
user = self.call('core_cohort_add_cohort_members', members=members)
|
||||
def add_user_to_cohort(self, userid, cohortid):
|
||||
members = [
|
||||
{
|
||||
"cohorttype": {"type": "id", "value": cohortid},
|
||||
"usertype": {"type": "id", "value": userid},
|
||||
}
|
||||
]
|
||||
user = self.call("core_cohort_add_cohort_members", members=members)
|
||||
return user
|
||||
|
||||
def delete_user_in_cohort(self,userid,cohortid):
|
||||
members=[{'cohortid':cohortid,
|
||||
'userid':userid}]
|
||||
user = self.call('core_cohort_delete_cohort_members', members=members)
|
||||
def delete_user_in_cohort(self, userid, cohortid):
|
||||
members = [{"cohortid": cohortid, "userid": userid}]
|
||||
user = self.call("core_cohort_delete_cohort_members", members=members)
|
||||
return user
|
||||
|
||||
def get_cohort_members(self, cohort_ids):
|
||||
members = self.call('core_cohort_get_cohort_members', cohortids=cohort_ids)
|
||||
#[0]['userids']
|
||||
members = self.call("core_cohort_get_cohort_members", cohortids=cohort_ids)
|
||||
# [0]['userids']
|
||||
return members
|
||||
|
||||
def delete_cohorts(self, cohortids):
|
||||
deleted = self.call('core_cohort_delete_cohorts', cohortids=cohortids)
|
||||
deleted = self.call("core_cohort_delete_cohorts", cohortids=cohortids)
|
||||
return deleted
|
||||
|
||||
def get_user_cohorts(self, user_id):
|
||||
user_cohorts=[]
|
||||
cohorts=self.get_cohorts()
|
||||
user_cohorts = []
|
||||
cohorts = self.get_cohorts()
|
||||
for cohort in cohorts:
|
||||
if user_id in self.get_cohort_members(cohort['id']): user_cohorts.append(cohort)
|
||||
if user_id in self.get_cohort_members(cohort["id"]):
|
||||
user_cohorts.append(cohort)
|
||||
return user_cohorts
|
||||
|
||||
def add_user_to_siteadmin(self,user_id):
|
||||
def add_user_to_siteadmin(self, user_id):
|
||||
q = """SELECT value FROM mdl_config WHERE name='siteadmins'"""
|
||||
value=self.moodle_pg.select(q)[0][0]
|
||||
value = self.moodle_pg.select(q)[0][0]
|
||||
if str(user_id) not in value:
|
||||
value=value+','+str(user_id)
|
||||
q = """UPDATE mdl_config SET value = '%s' WHERE name='siteadmins'""" % (value)
|
||||
value = value + "," + str(user_id)
|
||||
q = """UPDATE mdl_config SET value = '%s' WHERE name='siteadmins'""" % (
|
||||
value
|
||||
)
|
||||
self.moodle_pg.update(q)
|
||||
log.warning('MOODLE:ADDING THE USER TO ADMINS: This needs a purge cache in moodle!')
|
||||
log.warning(
|
||||
"MOODLE:ADDING THE USER TO ADMINS: This needs a purge cache in moodle!"
|
||||
)
|
||||
|
||||
# def add_role_to_user(self, user_id, role='admin', context='missing'):
|
||||
# if role=='admin':
|
||||
# if role=='admin':
|
||||
# role_id=1
|
||||
# else:
|
||||
# return False
|
||||
# assignments = [{'roleid': role_id, 'userid': user_id, 'contextid': 0}]
|
||||
# self.call('core_role_assign_roles', assignments=assignments)
|
||||
# userid=user_id, role_id=role_id)
|
||||
# 'contextlevel': 1,
|
||||
# userid=user_id, role_id=role_id)
|
||||
# 'contextlevel': 1,
|
||||
|
||||
|
||||
# define('CONTEXT_SYSTEM', 10);
|
||||
# define('CONTEXT_USER', 30);
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import json
|
||||
import logging as log
|
||||
import time
|
||||
from admin import app
|
||||
import traceback
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import logging as log
|
||||
import traceback
|
||||
import yaml, json
|
||||
|
||||
import mysql.connector
|
||||
import yaml
|
||||
|
||||
class Mysql():
|
||||
from admin import app
|
||||
|
||||
def __init__(self,host,database,user,password):
|
||||
|
||||
class Mysql:
|
||||
def __init__(self, host, database, user, password):
|
||||
self.conn = mysql.connector.connect(
|
||||
host=host,
|
||||
database=database,
|
||||
user=user,
|
||||
password=password)
|
||||
host=host, database=database, user=user, password=password
|
||||
)
|
||||
|
||||
def select(self,sql):
|
||||
def select(self, sql):
|
||||
self.cur = self.conn.cursor()
|
||||
self.cur.execute(sql)
|
||||
data=self.cur.fetchall()
|
||||
data = self.cur.fetchall()
|
||||
self.cur.close()
|
||||
return data
|
||||
|
||||
def update(self,sql):
|
||||
def update(self, sql):
|
||||
self.cur = self.conn.cursor()
|
||||
self.cur.execute(sql)
|
||||
self.conn.commit()
|
||||
|
|
|
@ -1,38 +1,63 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
|
||||
#from ..lib.log import *
|
||||
from admin import app
|
||||
import time,requests,json,pprint,os
|
||||
import urllib
|
||||
import traceback
|
||||
import json
|
||||
import logging as log
|
||||
from .nextcloud_exc import *
|
||||
import os
|
||||
import pprint
|
||||
import time
|
||||
import traceback
|
||||
import urllib
|
||||
|
||||
import requests
|
||||
|
||||
# from ..lib.log import *
|
||||
from admin import app
|
||||
|
||||
from .nextcloud_exc import *
|
||||
from .postgres import Postgres
|
||||
|
||||
class Nextcloud():
|
||||
def __init__(self,
|
||||
url="https://nextcloud."+app.config['DOMAIN'],
|
||||
username=os.environ['NEXTCLOUD_ADMIN_USER'],
|
||||
password=os.environ['NEXTCLOUD_ADMIN_PASSWORD'],
|
||||
verify=True):
|
||||
|
||||
self.verify_cert=verify
|
||||
self.apiurl=url+'/ocs/v1.php/cloud/'
|
||||
self.shareurl=url+'/ocs/v2.php/apps/files_sharing/api/v1/'
|
||||
self.davurl=url+'/remote.php/dav/files/'
|
||||
self.auth=(username,password)
|
||||
self.user=username
|
||||
class Nextcloud:
|
||||
def __init__(
|
||||
self,
|
||||
url="https://nextcloud." + app.config["DOMAIN"],
|
||||
username=os.environ["NEXTCLOUD_ADMIN_USER"],
|
||||
password=os.environ["NEXTCLOUD_ADMIN_PASSWORD"],
|
||||
verify=True,
|
||||
):
|
||||
|
||||
self.nextcloud_pg=Postgres('isard-apps-postgresql','nextcloud',app.config['NEXTCLOUD_POSTGRES_USER'],app.config['NEXTCLOUD_POSTGRES_PASSWORD'])
|
||||
self.verify_cert = verify
|
||||
self.apiurl = url + "/ocs/v1.php/cloud/"
|
||||
self.shareurl = url + "/ocs/v2.php/apps/files_sharing/api/v1/"
|
||||
self.davurl = url + "/remote.php/dav/files/"
|
||||
self.auth = (username, password)
|
||||
self.user = username
|
||||
|
||||
def _request(self,method,url,data={},headers={'OCS-APIRequest':'true'},auth=False):
|
||||
if auth == False: auth=self.auth
|
||||
self.nextcloud_pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"nextcloud",
|
||||
app.config["NEXTCLOUD_POSTGRES_USER"],
|
||||
app.config["NEXTCLOUD_POSTGRES_PASSWORD"],
|
||||
)
|
||||
|
||||
def _request(
|
||||
self, method, url, data={}, headers={"OCS-APIRequest": "true"}, auth=False
|
||||
):
|
||||
if auth == False:
|
||||
auth = self.auth
|
||||
try:
|
||||
response = requests.request(method, url, data=data, auth=auth, verify=self.verify_cert, headers=headers)
|
||||
if 'meta' in response.text:
|
||||
if '<statuscode>997</statuscode>' in response.text: raise ProviderUnauthorized
|
||||
response = requests.request(
|
||||
method,
|
||||
url,
|
||||
data=data,
|
||||
auth=auth,
|
||||
verify=self.verify_cert,
|
||||
headers=headers,
|
||||
)
|
||||
if "meta" in response.text:
|
||||
if "<statuscode>997</statuscode>" in response.text:
|
||||
raise ProviderUnauthorized
|
||||
# if '<statuscode>998</statuscode>' in response.text: raise ProviderInvalidQuery
|
||||
return response.text
|
||||
|
||||
|
@ -48,15 +73,16 @@ class Nextcloud():
|
|||
# except requests.exceptions.RequestException as err:
|
||||
# raise ProviderError
|
||||
except Exception as e:
|
||||
if str(e) == 'an integer is required (got type bytes)':
|
||||
if str(e) == "an integer is required (got type bytes)":
|
||||
raise ProviderConnError
|
||||
raise ProviderError
|
||||
|
||||
def check_connection(self):
|
||||
url = self.apiurl + "users/"+self.user+"?format=json"
|
||||
url = self.apiurl + "users/" + self.user + "?format=json"
|
||||
try:
|
||||
result = self._request('GET',url)
|
||||
if json.loads(result)['ocs']['meta']['statuscode'] == 100: return True
|
||||
result = self._request("GET", url)
|
||||
if json.loads(result)["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
raise ProviderError
|
||||
except requests.exceptions.HTTPError as errh:
|
||||
raise ProviderConnError
|
||||
|
@ -69,43 +95,43 @@ class Nextcloud():
|
|||
except requests.exceptions.RequestException as err:
|
||||
raise ProviderError
|
||||
except Exception as e:
|
||||
if str(e) == 'an integer is required (got type bytes)':
|
||||
if str(e) == "an integer is required (got type bytes)":
|
||||
raise ProviderConnError
|
||||
raise ProviderError
|
||||
|
||||
def get_user(self,userid):
|
||||
url = self.apiurl + "users/"+userid+"?format=json"
|
||||
def get_user(self, userid):
|
||||
url = self.apiurl + "users/" + userid + "?format=json"
|
||||
try:
|
||||
result = json.loads(self._request('GET',url))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return result['ocs']['data']
|
||||
result = json.loads(self._request("GET", url))
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return result["ocs"]["data"]
|
||||
raise ProviderItemNotExists
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
# 100 - successful
|
||||
|
||||
# q = """select u.uid as username, adn.value as displayname, ade.value as email, json_agg(gg.displayname) as admin_groups,json_agg(g.displayname) as groups
|
||||
# from oc_users as u
|
||||
# left join oc_group_user as gu on gu.uid = u.uid
|
||||
# left join oc_groups as g on gu.gid = g.gid
|
||||
# left join oc_group_admin as ga on ga.uid = u.uid
|
||||
# left join oc_groups as gg on gg.gid = ga.gid
|
||||
# left join oc_accounts_data as adn on adn.uid = u.uid and adn.name = 'displayname'
|
||||
# left join oc_accounts_data as ade on ade.uid = u.uid and ade.name = 'email'
|
||||
# group by u.uid, adn.value, ade.value"""
|
||||
# cur.execute(q)
|
||||
# users = cur.fetchall()
|
||||
# fields = [a.name for a in cur.description]
|
||||
# cur.close()
|
||||
# conn.close()
|
||||
# q = """select u.uid as username, adn.value as displayname, ade.value as email, json_agg(gg.displayname) as admin_groups,json_agg(g.displayname) as groups
|
||||
# from oc_users as u
|
||||
# left join oc_group_user as gu on gu.uid = u.uid
|
||||
# left join oc_groups as g on gu.gid = g.gid
|
||||
# left join oc_group_admin as ga on ga.uid = u.uid
|
||||
# left join oc_groups as gg on gg.gid = ga.gid
|
||||
# left join oc_accounts_data as adn on adn.uid = u.uid and adn.name = 'displayname'
|
||||
# left join oc_accounts_data as ade on ade.uid = u.uid and ade.name = 'email'
|
||||
# group by u.uid, adn.value, ade.value"""
|
||||
# cur.execute(q)
|
||||
# users = cur.fetchall()
|
||||
# fields = [a.name for a in cur.description]
|
||||
# cur.close()
|
||||
# conn.close()
|
||||
|
||||
|
||||
# users_with_lists = [list(l[:-2])+([[]] if l[-2] == [None] else [list(set(l[-2]))]) + ([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users]
|
||||
# users_with_lists = [list(l[:-2])+([[]] if l[-2] == [None] else [list(set(l[-2]))]) + ([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users_with_lists]
|
||||
# list_dict_users = [dict(zip(fields, r)) for r in users_with_lists]
|
||||
# users_with_lists = [list(l[:-2])+([[]] if l[-2] == [None] else [list(set(l[-2]))]) + ([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users]
|
||||
# users_with_lists = [list(l[:-2])+([[]] if l[-2] == [None] else [list(set(l[-2]))]) + ([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users_with_lists]
|
||||
# list_dict_users = [dict(zip(fields, r)) for r in users_with_lists]
|
||||
def get_users_list(self):
|
||||
# q = """select u.uid as username, adn.value as displayname, ade.value as email, json_agg(gg.displayname) as admin_groups,json_agg(g.displayname) as groups
|
||||
# from oc_users as u
|
||||
# from oc_users as u
|
||||
# left join oc_group_user as gu on gu.uid = u.uid
|
||||
# left join oc_groups as g on gu.gid = g.gid
|
||||
# left join oc_group_admin as ga on ga.uid = u.uid
|
||||
|
@ -127,9 +153,19 @@ class Nextcloud():
|
|||
left join oc_storages as s on s.id=CONCAT('home::',u.uid)
|
||||
left join oc_filecache as fc on fc.storage = numeric_id
|
||||
group by u.uid, adn.value, ade.value, pref.configvalue"""
|
||||
(headers,users)=self.nextcloud_pg.select_with_headers(q)
|
||||
users_with_lists = [list(l[:-2])+([[]] if l[-2] == [None] else [list(set(l[-2]))]) + ([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users]
|
||||
users_with_lists = [list(l[:-2])+([[]] if l[-2] == [None] else [list(set(l[-2]))]) + ([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users_with_lists]
|
||||
(headers, users) = self.nextcloud_pg.select_with_headers(q)
|
||||
users_with_lists = [
|
||||
list(l[:-2])
|
||||
+ ([[]] if l[-2] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users
|
||||
]
|
||||
users_with_lists = [
|
||||
list(l[:-2])
|
||||
+ ([[]] if l[-2] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users_with_lists
|
||||
]
|
||||
list_dict_users = [dict(zip(headers, r)) for r in users_with_lists]
|
||||
return list_dict_users
|
||||
|
||||
|
@ -143,33 +179,46 @@ class Nextcloud():
|
|||
# raise ProviderOpError
|
||||
# except:
|
||||
# log.error(traceback.format_exc())
|
||||
# raise
|
||||
# raise
|
||||
|
||||
def add_user(self,userid,userpassword,quota=False,group=False,email='',displayname=''):
|
||||
data={'userid':userid,'password':userpassword,'quota':quota,'groups[]':group,'email':email,'displayname':displayname}
|
||||
if not group: del data['groups[]']
|
||||
if not quota: del data['quota']
|
||||
def add_user(
|
||||
self, userid, userpassword, quota=False, group=False, email="", displayname=""
|
||||
):
|
||||
data = {
|
||||
"userid": userid,
|
||||
"password": userpassword,
|
||||
"quota": quota,
|
||||
"groups[]": group,
|
||||
"email": email,
|
||||
"displayname": displayname,
|
||||
}
|
||||
if not group:
|
||||
del data["groups[]"]
|
||||
if not quota:
|
||||
del data["quota"]
|
||||
# if group:
|
||||
# data={'userid':userid,'password':userpassword,'quota':quota,'groups[]':group,'email':email,'displayname':displayname}
|
||||
# else:
|
||||
# data={'userid':userid,'password':userpassword,'quota':quota,'email':email,'displayname':displayname}
|
||||
url = self.apiurl + "users?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = json.loads(self._request('POST',url,data=data,headers=headers))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return True
|
||||
if result['ocs']['meta']['statuscode'] == 102: raise ProviderItemExists
|
||||
if result['ocs']['meta']['statuscode'] == 104:
|
||||
result = json.loads(self._request("POST", url, data=data, headers=headers))
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
if result["ocs"]["meta"]["statuscode"] == 102:
|
||||
raise ProviderItemExists
|
||||
if result["ocs"]["meta"]["statuscode"] == 104:
|
||||
self.add_group(group)
|
||||
# raise ProviderGroupNotExists
|
||||
log.error('Get Nextcloud provider user add error: '+str(result))
|
||||
log.error("Get Nextcloud provider user add error: " + str(result))
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
# 100 - successful
|
||||
# 101 - invalid input data
|
||||
# 102 - username already exists
|
||||
|
@ -184,89 +233,113 @@ class Nextcloud():
|
|||
|
||||
url = self.apiurl + "users/" + userid + "?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
for k,v in key_values.items():
|
||||
data={"key":k,"value":v}
|
||||
for k, v in key_values.items():
|
||||
data = {"key": k, "value": v}
|
||||
|
||||
try:
|
||||
result = json.loads(self._request('PUT',url,data=data,headers=headers))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return True
|
||||
if result['ocs']['meta']['statuscode'] == 102: raise ProviderItemExists
|
||||
if result['ocs']['meta']['statuscode'] == 104: raise ProviderGroupNotExists
|
||||
log.error('Get Nextcloud provider user add error: '+str(result))
|
||||
result = json.loads(
|
||||
self._request("PUT", url, data=data, headers=headers)
|
||||
)
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
if result["ocs"]["meta"]["statuscode"] == 102:
|
||||
raise ProviderItemExists
|
||||
if result["ocs"]["meta"]["statuscode"] == 104:
|
||||
raise ProviderGroupNotExists
|
||||
log.error("Get Nextcloud provider user add error: " + str(result))
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
|
||||
def add_user_to_group(self, userid, group_id):
|
||||
data={'groupid':group_id}
|
||||
data = {"groupid": group_id}
|
||||
|
||||
url = self.apiurl + "users/" + userid + "/groups?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = json.loads(self._request('POST',url,data=data,headers=headers))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return True
|
||||
if result['ocs']['meta']['statuscode'] == 102: raise ProviderItemExists
|
||||
if result['ocs']['meta']['statuscode'] == 104: raise ProviderGroupNotExists
|
||||
log.error('Get Nextcloud provider user add error: '+str(result))
|
||||
result = json.loads(self._request("POST", url, data=data, headers=headers))
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
if result["ocs"]["meta"]["statuscode"] == 102:
|
||||
raise ProviderItemExists
|
||||
if result["ocs"]["meta"]["statuscode"] == 104:
|
||||
raise ProviderGroupNotExists
|
||||
log.error("Get Nextcloud provider user add error: " + str(result))
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
|
||||
def remove_user_from_group(self, userid, group_id):
|
||||
data={'groupid':group_id}
|
||||
data = {"groupid": group_id}
|
||||
|
||||
url = self.apiurl + "users/" + userid + "/groups?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = json.loads(self._request('DELETE',url,data=data,headers=headers))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return True
|
||||
if result['ocs']['meta']['statuscode'] == 102: raise ProviderItemExists
|
||||
if result['ocs']['meta']['statuscode'] == 104:
|
||||
result = json.loads(
|
||||
self._request("DELETE", url, data=data, headers=headers)
|
||||
)
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
if result["ocs"]["meta"]["statuscode"] == 102:
|
||||
raise ProviderItemExists
|
||||
if result["ocs"]["meta"]["statuscode"] == 104:
|
||||
self.add_group(group)
|
||||
# raise ProviderGroupNotExists
|
||||
log.error('Get Nextcloud provider user add error: '+str(result))
|
||||
log.error("Get Nextcloud provider user add error: " + str(result))
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
|
||||
def add_user_with_groups(self,userid,userpassword,quota=False,groups=[],email='',displayname=''):
|
||||
data={'userid':userid,'password':userpassword,'quota':quota,'groups[]':groups,'email':email,'displayname':displayname}
|
||||
def add_user_with_groups(
|
||||
self, userid, userpassword, quota=False, groups=[], email="", displayname=""
|
||||
):
|
||||
data = {
|
||||
"userid": userid,
|
||||
"password": userpassword,
|
||||
"quota": quota,
|
||||
"groups[]": groups,
|
||||
"email": email,
|
||||
"displayname": displayname,
|
||||
}
|
||||
# if not group: del data['groups[]']
|
||||
if not quota: del data['quota']
|
||||
if not quota:
|
||||
del data["quota"]
|
||||
# if group:
|
||||
# data={'userid':userid,'password':userpassword,'quota':quota,'groups[]':group,'email':email,'displayname':displayname}
|
||||
# else:
|
||||
# data={'userid':userid,'password':userpassword,'quota':quota,'email':email,'displayname':displayname}
|
||||
url = self.apiurl + "users?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = json.loads(self._request('POST',url,data=data,headers=headers))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return True
|
||||
if result['ocs']['meta']['statuscode'] == 102: raise ProviderItemExists
|
||||
if result['ocs']['meta']['statuscode'] == 104:
|
||||
result = json.loads(self._request("POST", url, data=data, headers=headers))
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
if result["ocs"]["meta"]["statuscode"] == 102:
|
||||
raise ProviderItemExists
|
||||
if result["ocs"]["meta"]["statuscode"] == 104:
|
||||
# self.add_group(group)
|
||||
None
|
||||
# raise ProviderGroupNotExists
|
||||
log.error('Get Nextcloud provider user add error: '+str(result))
|
||||
log.error("Get Nextcloud provider user add error: " + str(result))
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
# 100 - successful
|
||||
# 101 - invalid input data
|
||||
# 102 - username already exists
|
||||
|
@ -276,149 +349,172 @@ class Nextcloud():
|
|||
# 106 - no group specified (required for subadmins)
|
||||
# 107 - all errors that contain a hint - for example “Password is among the 1,000,000 most common ones. Please make it unique.” (this code was added in 12.0.6 & 13.0.1)
|
||||
|
||||
def delete_user(self,userid):
|
||||
url = self.apiurl + "users/"+userid+"?format=json"
|
||||
def delete_user(self, userid):
|
||||
url = self.apiurl + "users/" + userid + "?format=json"
|
||||
try:
|
||||
result = json.loads(self._request('DELETE',url))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return True
|
||||
if result['ocs']['meta']['statuscode'] == 101: raise ProviderUserNotExists
|
||||
result = json.loads(self._request("DELETE", url))
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
if result["ocs"]["meta"]["statuscode"] == 101:
|
||||
raise ProviderUserNotExists
|
||||
log.error(traceback.format_exc())
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
# 100 - successful
|
||||
# 101 - failure
|
||||
|
||||
def enable_user(self,userid):
|
||||
def enable_user(self, userid):
|
||||
None
|
||||
|
||||
def disable_user(self,userid):
|
||||
def disable_user(self, userid):
|
||||
None
|
||||
|
||||
def exists_user_folder(self,userid,userpassword,folder='IsardVDI'):
|
||||
auth=(userid,userpassword)
|
||||
url = self.davurl + userid +"/" + folder+"?format=json"
|
||||
def exists_user_folder(self, userid, userpassword, folder="IsardVDI"):
|
||||
auth = (userid, userpassword)
|
||||
url = self.davurl + userid + "/" + folder + "?format=json"
|
||||
headers = {
|
||||
'Depth': '0',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Depth": "0",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = self._request('PROPFIND',url,auth=auth,headers=headers)
|
||||
if '<d:status>HTTP/1.1 200 OK</d:status>' in result: return True
|
||||
result = self._request("PROPFIND", url, auth=auth, headers=headers)
|
||||
if "<d:status>HTTP/1.1 200 OK</d:status>" in result:
|
||||
return True
|
||||
return False
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
|
||||
def add_user_folder(self,userid,userpassword,folder='IsardVDI'):
|
||||
auth=(userid,userpassword)
|
||||
url = self.davurl + userid +"/" + folder+"?format=json"
|
||||
def add_user_folder(self, userid, userpassword, folder="IsardVDI"):
|
||||
auth = (userid, userpassword)
|
||||
url = self.davurl + userid + "/" + folder + "?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = self._request('MKCOL',url,auth=auth,headers=headers)
|
||||
if result=='': return True
|
||||
if '<s:message>The resource you tried to create already exists</s:message>' in result: raise ProviderItemExists
|
||||
log.error(result.split('message>')[1].split('<')[0])
|
||||
result = self._request("MKCOL", url, auth=auth, headers=headers)
|
||||
if result == "":
|
||||
return True
|
||||
if (
|
||||
"<s:message>The resource you tried to create already exists</s:message>"
|
||||
in result
|
||||
):
|
||||
raise ProviderItemExists
|
||||
log.error(result.split("message>")[1].split("<")[0])
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
|
||||
def exists_user_share_folder(self,userid,userpassword,folder='IsardVDI'):
|
||||
auth=(userid,userpassword)
|
||||
def exists_user_share_folder(self, userid, userpassword, folder="IsardVDI"):
|
||||
auth = (userid, userpassword)
|
||||
url = self.shareurl + "shares?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = json.loads(self._request('GET', url, auth=auth, headers=headers))
|
||||
if result['ocs']['meta']['statuscode']==200:
|
||||
share=[s for s in result['ocs']['data'] if s['path'] == '/'+folder]
|
||||
result = json.loads(self._request("GET", url, auth=auth, headers=headers))
|
||||
if result["ocs"]["meta"]["statuscode"] == 200:
|
||||
share = [s for s in result["ocs"]["data"] if s["path"] == "/" + folder]
|
||||
if len(share) >= 1:
|
||||
# Should we delete all but the first (0) one?
|
||||
return {'token': share[0]['token'],
|
||||
'url': share[0]['url']}
|
||||
return {"token": share[0]["token"], "url": share[0]["url"]}
|
||||
raise ProviderItemNotExists
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
|
||||
def add_user_share_folder(self,userid,userpassword,folder='IsardVDI'):
|
||||
auth=(userid,userpassword)
|
||||
data={'path':'/'+folder,'shareType':3}
|
||||
def add_user_share_folder(self, userid, userpassword, folder="IsardVDI"):
|
||||
auth = (userid, userpassword)
|
||||
data = {"path": "/" + folder, "shareType": 3}
|
||||
url = self.shareurl + "shares?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = json.loads(self._request('POST',url, data=data, auth=auth, headers=headers))
|
||||
if result['ocs']['meta']['statuscode'] == 100 or result['ocs']['meta']['statuscode'] == 200:
|
||||
return {'token': result['ocs']['data']['token'],
|
||||
'url': result['ocs']['data']['url']}
|
||||
log.error('Add user share folder error: '+result['ocs']['meta']['message'])
|
||||
result = json.loads(
|
||||
self._request("POST", url, data=data, auth=auth, headers=headers)
|
||||
)
|
||||
if (
|
||||
result["ocs"]["meta"]["statuscode"] == 100
|
||||
or result["ocs"]["meta"]["statuscode"] == 200
|
||||
):
|
||||
return {
|
||||
"token": result["ocs"]["data"]["token"],
|
||||
"url": result["ocs"]["data"]["url"],
|
||||
}
|
||||
log.error(
|
||||
"Add user share folder error: " + result["ocs"]["meta"]["message"]
|
||||
)
|
||||
raise ProviderFolderNotExists
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
|
||||
def get_group(self,userid):
|
||||
def get_group(self, userid):
|
||||
None
|
||||
|
||||
def get_groups_list(self):
|
||||
url = self.apiurl + "groups?format=json"
|
||||
try:
|
||||
result = json.loads(self._request('GET',url))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return [g for g in result['ocs']['data']['groups']]
|
||||
result = json.loads(self._request("GET", url))
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return [g for g in result["ocs"]["data"]["groups"]]
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
|
||||
def add_group(self,groupid):
|
||||
data={'groupid':groupid}
|
||||
def add_group(self, groupid):
|
||||
data = {"groupid": groupid}
|
||||
url = self.apiurl + "groups?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = json.loads(self._request('POST',url, data=data, auth=self.auth, headers=headers))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return True
|
||||
if result['ocs']['meta']['statuscode'] == 102: raise ProviderItemExists
|
||||
result = json.loads(
|
||||
self._request("POST", url, data=data, auth=self.auth, headers=headers)
|
||||
)
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
if result["ocs"]["meta"]["statuscode"] == 102:
|
||||
raise ProviderItemExists
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
# 100 - successful
|
||||
# 101 - invalid input data
|
||||
# 102 - group already exists
|
||||
# 103 - failed to add the group
|
||||
|
||||
def delete_group(self,groupid):
|
||||
group = urllib.parse.quote(groupid, safe='')
|
||||
url = self.apiurl + "groups/"+group+"?format=json"
|
||||
def delete_group(self, groupid):
|
||||
group = urllib.parse.quote(groupid, safe="")
|
||||
url = self.apiurl + "groups/" + group + "?format=json"
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'OCS-APIRequest': 'true',
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"OCS-APIRequest": "true",
|
||||
}
|
||||
try:
|
||||
result = json.loads(self._request('DELETE',url, auth=self.auth, headers=headers))
|
||||
if result['ocs']['meta']['statuscode'] == 100: return True
|
||||
result = json.loads(
|
||||
self._request("DELETE", url, auth=self.auth, headers=headers)
|
||||
)
|
||||
if result["ocs"]["meta"]["statuscode"] == 100:
|
||||
return True
|
||||
log.error(traceback.format_exc())
|
||||
raise ProviderOpError
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
raise
|
||||
raise
|
||||
# 100 - successful
|
||||
# 101 - invalid input data
|
||||
# 102 - group already exists
|
||||
# 103 - failed to add the group
|
||||
|
||||
|
|
|
@ -3,30 +3,38 @@
|
|||
class ProviderUnauthorized(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderConnError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderSslError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderConnTimeout(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderItemExists(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderItemNotExists(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderGroupNotExists(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProviderFolderNotExists(Exception):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class ProviderOpError(Exception):
|
||||
pass
|
||||
|
|
|
@ -1,50 +1,48 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import json
|
||||
import logging as log
|
||||
import time
|
||||
from admin import app
|
||||
import traceback
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import logging as log
|
||||
import traceback
|
||||
import yaml, json
|
||||
|
||||
import psycopg2
|
||||
import yaml
|
||||
|
||||
class Postgres():
|
||||
from admin import app
|
||||
|
||||
def __init__(self,host,database,user,password):
|
||||
|
||||
class Postgres:
|
||||
def __init__(self, host, database, user, password):
|
||||
self.conn = psycopg2.connect(
|
||||
host=host,
|
||||
database=database,
|
||||
user=user,
|
||||
password=password)
|
||||
|
||||
host=host, database=database, user=user, password=password
|
||||
)
|
||||
|
||||
# def __del__(self):
|
||||
# self.cur.close()
|
||||
# self.conn.close()
|
||||
|
||||
def select(self,sql):
|
||||
def select(self, sql):
|
||||
self.cur = self.conn.cursor()
|
||||
self.cur.execute(sql)
|
||||
data=self.cur.fetchall()
|
||||
data = self.cur.fetchall()
|
||||
self.cur.close()
|
||||
return data
|
||||
|
||||
def update(self,sql):
|
||||
def update(self, sql):
|
||||
self.cur = self.conn.cursor()
|
||||
self.cur.execute(sql)
|
||||
self.conn.commit()
|
||||
self.cur.close()
|
||||
# return self.cur.fetchall()
|
||||
|
||||
def select_with_headers(self,sql):
|
||||
def select_with_headers(self, sql):
|
||||
self.cur = self.conn.cursor()
|
||||
self.cur.execute(sql)
|
||||
data=self.cur.fetchall()
|
||||
data = self.cur.fetchall()
|
||||
fields = [a.name for a in self.cur.description]
|
||||
self.cur.close()
|
||||
return (fields,data)
|
||||
return (fields, data)
|
||||
|
||||
# def update_moodle_saml_plugin(self):
|
||||
# plugin[('idpmetadata', '<md:EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Name="urn:keycloak"><md:EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://sso.'+app.config['DOMAIN']+'/auth/realms/master"><md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo><ds:KeyName>NrtA5ynG0htowP3SXw7dBJRIAMxn-1PwuuXwOwNhlRw</ds:KeyName><ds:X509Data><ds:X509Certificate>MIICmzCCAYMCBgF5jb0RCTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjEwNTIxMDcwMjI4WhcNMzEwNTIxMDcwNDA4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCI8xh/C0+frz3kgWiUbziTDls71R2YiXLSVE+bw7gbEgZUGCLhoEI679azMtIxmnzM/snIX+yTb12+XoYkgbiLTMPQfnH+Kiab6g3HL3KPfhqS+yWkFxOoCp6Ibmp7yPlVWuHH+MBfO8OBr/r8Ao7heFbuzjiLd1KG67rcoaxfDgMuBoEomg1bgEjFgHaQIrSC6OZzH0h987/arqufZXeXlfyiqScMPUi+u5IpDWSwz06UKP0k8mxzNSlpZ93CKOUSsV0SMLxqg7FQ3SGiOk577bGW9o9BDTkkmSo3Up6smc0LzwvvUwuNd0B1irGkWZFQN9OXJnJYf1InEebIMtmPAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADM34+qEGeBQ22luphVTuVJtGxcbxLx7DfsT0QfJD/OuxTTbNAa1VRyarb5juIAkqdj4y2quZna9ZXLecVo4RkwpzPoKoAkYA8b+kHnWqEwJi9iPrDvKb+GR0bBkLPN49YxIZ8IdKX/PRa3yuLHe+loiNsCaS/2ZK2KO46COsqU4QX1iVhF9kWphNLybjNAX45B6cJLsa1g0vXLdm3kv3SB4I2fErFVaOoDtFIjttoYlXdpUiThkPXBfr7N67P3dZHaS4tjJh+IZ8I6TINpcsH8dBkUhzYEIPHCePwSiC1w6WDBLNDuKt1mj1CZrLq+1x+Yhrs+QNRheEKGi89HZ8N0=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml/resolve" index="0"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/></md:IDPSSODescriptor></md:EntityDescriptor></md:EntitiesDescriptor>')]
|
||||
|
@ -52,4 +50,4 @@ class Postgres():
|
|||
# cursor.execute(pg_update, (title, bookid))
|
||||
# connection.commit()
|
||||
# count = cursor.rowcount
|
||||
# print(count, "Successfully Updated!")
|
||||
# print(count, "Successfully Updated!")
|
||||
|
|
|
@ -1,74 +1,111 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import time, os
|
||||
from admin import app
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import traceback
|
||||
import yaml, json
|
||||
import os
|
||||
import random
|
||||
|
||||
import psycopg2
|
||||
|
||||
from .postgres import Postgres
|
||||
# from .keycloak import Keycloak
|
||||
# from .moodle import Moodle
|
||||
import string, random
|
||||
import string
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
class Postup():
|
||||
import psycopg2
|
||||
import yaml
|
||||
|
||||
from admin import app
|
||||
|
||||
from .postgres import Postgres
|
||||
|
||||
|
||||
class Postup:
|
||||
def __init__(self):
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
self.pg=Postgres('isard-apps-postgresql','moodle',app.config['MOODLE_POSTGRES_USER'],app.config['MOODLE_POSTGRES_PASSWORD'])
|
||||
ready=True
|
||||
self.pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"moodle",
|
||||
app.config["MOODLE_POSTGRES_USER"],
|
||||
app.config["MOODLE_POSTGRES_PASSWORD"],
|
||||
)
|
||||
ready = True
|
||||
except:
|
||||
log.warning('Could not connect to moodle database. Retrying...')
|
||||
log.warning("Could not connect to moodle database. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Connected to moodle database.')
|
||||
log.info("Connected to moodle database.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join(app.root_path, "../moodledata/saml2/moodle."+app.config['DOMAIN']+".crt"),"r") as crt:
|
||||
app.config.setdefault('SP_CRT', crt.read())
|
||||
ready=True
|
||||
with open(
|
||||
os.path.join(
|
||||
app.root_path,
|
||||
"../moodledata/saml2/moodle." + app.config["DOMAIN"] + ".crt",
|
||||
),
|
||||
"r",
|
||||
) as crt:
|
||||
app.config.setdefault("SP_CRT", crt.read())
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get moodle SAML2 crt certificate. Retrying...')
|
||||
log.warning("Could not get moodle SAML2 crt certificate. Retrying...")
|
||||
time.sleep(2)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
log.info('Got moodle srt certificate.')
|
||||
log.info("Got moodle srt certificate.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join(app.root_path, "../moodledata/saml2/moodle."+app.config['DOMAIN']+".pem"),"r") as pem:
|
||||
app.config.setdefault('SP_PEM', pem.read())
|
||||
ready=True
|
||||
with open(
|
||||
os.path.join(
|
||||
app.root_path,
|
||||
"../moodledata/saml2/moodle." + app.config["DOMAIN"] + ".pem",
|
||||
),
|
||||
"r",
|
||||
) as pem:
|
||||
app.config.setdefault("SP_PEM", pem.read())
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get moodle SAML2 pem certificate. Retrying...')
|
||||
log.warning("Could not get moodle SAML2 pem certificate. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Got moodle pem certificate.')
|
||||
log.info("Got moodle pem certificate.")
|
||||
|
||||
self.select_and_configure_theme()
|
||||
self.configure_tipnc()
|
||||
self.add_moodle_ws_token()
|
||||
|
||||
def select_and_configure_theme(self,theme='cbe'):
|
||||
def select_and_configure_theme(self, theme="cbe"):
|
||||
try:
|
||||
self.pg.update("""UPDATE "mdl_config" SET value = '%s' WHERE "name" = 'theme';""" % (theme))
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config" SET value = '%s' WHERE "name" = 'theme';"""
|
||||
% (theme)
|
||||
)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
exit(1)
|
||||
None
|
||||
|
||||
try:
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'theme_cbe' AND "name" = 'host';""" % (os.environ['DOMAIN']))
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'theme_cbe' AND "name" = 'logourl';""" % ("https://api."+os.environ['DOMAIN']+"/img/logo.png"))
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'header_api';""")
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'vclasses_direct';""")
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'uniquenamecourse';""")
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'theme_cbe' AND "name" = 'host';"""
|
||||
% (os.environ["DOMAIN"])
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'theme_cbe' AND "name" = 'logourl';"""
|
||||
% ("https://api." + os.environ["DOMAIN"] + "/img/logo.png")
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'header_api';"""
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'vclasses_direct';"""
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '1' WHERE "plugin" = 'theme_cbe' AND "name" = 'uniquenamecourse';"""
|
||||
)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
exit(1)
|
||||
|
@ -76,12 +113,27 @@ class Postup():
|
|||
|
||||
def configure_tipnc(self):
|
||||
try:
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'host';""" % ("https://nextcloud."+os.environ['DOMAIN']+"/"))
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'password';""" % (os.environ['NEXTCLOUD_ADMIN_PASSWORD']))
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = 'template.docx' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'template';""")
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '/apps/onlyoffice/' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'location';""")
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'user';""" % (os.environ['NEXTCLOUD_ADMIN_USER']))
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = 'tasks' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'folder';""")
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'host';"""
|
||||
% ("https://nextcloud." + os.environ["DOMAIN"] + "/")
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'password';"""
|
||||
% (os.environ["NEXTCLOUD_ADMIN_PASSWORD"])
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = 'template.docx' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'template';"""
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '/apps/onlyoffice/' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'location';"""
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'user';"""
|
||||
% (os.environ["NEXTCLOUD_ADMIN_USER"])
|
||||
)
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = 'tasks' WHERE "plugin" = 'assignsubmission_tipnc' AND "name" = 'folder';"""
|
||||
)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
exit(1)
|
||||
|
@ -89,18 +141,23 @@ class Postup():
|
|||
|
||||
def add_moodle_ws_token(self):
|
||||
try:
|
||||
token=self.pg.select("""SELECT * FROM "mdl_external_tokens" WHERE "externalserviceid" = 3""")[0][1]
|
||||
app.config.setdefault('MOODLE_WS_TOKEN',token)
|
||||
token = self.pg.select(
|
||||
"""SELECT * FROM "mdl_external_tokens" WHERE "externalserviceid" = 3"""
|
||||
)[0][1]
|
||||
app.config.setdefault("MOODLE_WS_TOKEN", token)
|
||||
return
|
||||
except:
|
||||
# log.error(traceback.format_exc())
|
||||
None
|
||||
|
||||
try:
|
||||
self.pg.update("""INSERT INTO "mdl_external_services" ("name", "enabled", "requiredcapability", "restrictedusers", "component", "timecreated", "timemodified", "shortname", "downloadfiles", "uploadfiles") VALUES
|
||||
('dd admin', 1, '', 1, NULL, 1621719763, 1621719850, 'dd_admin', 0, 0);""")
|
||||
self.pg.update(
|
||||
"""INSERT INTO "mdl_external_services" ("name", "enabled", "requiredcapability", "restrictedusers", "component", "timecreated", "timemodified", "shortname", "downloadfiles", "uploadfiles") VALUES
|
||||
('dd admin', 1, '', 1, NULL, 1621719763, 1621719850, 'dd_admin', 0, 0);"""
|
||||
)
|
||||
|
||||
self.pg.update("""INSERT INTO "mdl_external_services_functions" ("externalserviceid", "functionname") VALUES
|
||||
self.pg.update(
|
||||
"""INSERT INTO "mdl_external_services_functions" ("externalserviceid", "functionname") VALUES
|
||||
(3, 'core_course_update_courses'),
|
||||
(3, 'core_user_get_users'),
|
||||
(3, 'core_user_get_users_by_field'),
|
||||
|
@ -116,17 +173,37 @@ class Postup():
|
|||
(3, 'core_cohort_search_cohorts'),
|
||||
(3, 'core_cohort_update_cohorts'),
|
||||
(3, 'core_role_assign_roles'),
|
||||
(3, 'core_cohort_get_cohorts');""")
|
||||
(3, 'core_cohort_get_cohorts');"""
|
||||
)
|
||||
|
||||
self.pg.update("""INSERT INTO "mdl_external_services_users" ("externalserviceid", "userid", "iprestriction", "validuntil", "timecreated") VALUES
|
||||
(3, 2, NULL, NULL, 1621719871);""")
|
||||
self.pg.update(
|
||||
"""INSERT INTO "mdl_external_services_users" ("externalserviceid", "userid", "iprestriction", "validuntil", "timecreated") VALUES
|
||||
(3, 2, NULL, NULL, 1621719871);"""
|
||||
)
|
||||
|
||||
b32=''.join(random.choices(string.ascii_uppercase + string.ascii_uppercase + string.ascii_lowercase, k = 32))
|
||||
b64=''.join(random.choices(string.ascii_uppercase + string.ascii_uppercase + string.ascii_lowercase, k = 64))
|
||||
self.pg.update("""INSERT INTO "mdl_external_tokens" ("token", "privatetoken", "tokentype", "userid", "externalserviceid", "sid", "contextid", "creatorid", "iprestriction", "validuntil", "timecreated", "lastaccess") VALUES
|
||||
('%s', '%s', 0, 2, 3, NULL, 1, 2, NULL, 0, 1621831206, NULL);""" % (b32,b64))
|
||||
|
||||
app.config.setdefault('MOODLE_WS_TOKEN',b32)
|
||||
b32 = "".join(
|
||||
random.choices(
|
||||
string.ascii_uppercase
|
||||
+ string.ascii_uppercase
|
||||
+ string.ascii_lowercase,
|
||||
k=32,
|
||||
)
|
||||
)
|
||||
b64 = "".join(
|
||||
random.choices(
|
||||
string.ascii_uppercase
|
||||
+ string.ascii_uppercase
|
||||
+ string.ascii_lowercase,
|
||||
k=64,
|
||||
)
|
||||
)
|
||||
self.pg.update(
|
||||
"""INSERT INTO "mdl_external_tokens" ("token", "privatetoken", "tokentype", "userid", "externalserviceid", "sid", "contextid", "creatorid", "iprestriction", "validuntil", "timecreated", "lastaccess") VALUES
|
||||
('%s', '%s', 0, 2, 3, NULL, 1, 2, NULL, 0, 1621831206, NULL);"""
|
||||
% (b32, b64)
|
||||
)
|
||||
|
||||
app.config.setdefault("MOODLE_WS_TOKEN", b32)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
exit(1)
|
||||
|
|
|
@ -1,315 +1,488 @@
|
|||
#!flask/bin/python
|
||||
# coding=utf-8
|
||||
from admin import app
|
||||
import logging as log
|
||||
import traceback
|
||||
|
||||
from uuid import uuid4
|
||||
import time,json
|
||||
import sys,os,re
|
||||
from flask import render_template, Response, request, redirect, url_for, jsonify
|
||||
import concurrent.futures
|
||||
from flask_login import current_user, login_required
|
||||
from .decorators import is_admin
|
||||
|
||||
from ..lib.helpers import system_group
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
# import Queue
|
||||
import threading
|
||||
threads={'external':None}
|
||||
import time
|
||||
import traceback
|
||||
from uuid import uuid4
|
||||
|
||||
from flask import Response, jsonify, redirect, render_template, request, url_for
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from admin import app
|
||||
|
||||
from ..lib.helpers import system_group
|
||||
from .decorators import is_admin
|
||||
|
||||
threads = {"external": None}
|
||||
# q = Queue.Queue()
|
||||
|
||||
from keycloak.exceptions import KeycloakGetError
|
||||
|
||||
from ..lib.exceptions import UserExists, UserNotFound
|
||||
|
||||
@app.route('/api/resync')
|
||||
|
||||
@app.route("/api/resync")
|
||||
@login_required
|
||||
def resync():
|
||||
return json.dumps(app.admin.resync_data()), 200, {'Content-Type': 'application/json'}
|
||||
return (
|
||||
json.dumps(app.admin.resync_data()),
|
||||
200,
|
||||
{"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
@app.route('/api/users', methods=['GET','PUT'])
|
||||
@app.route('/api/users/<provider>', methods=['POST', 'PUT', 'GET', 'DELETE'])
|
||||
|
||||
@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 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'}
|
||||
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
|
||||
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'}
|
||||
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({"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':
|
||||
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'}
|
||||
|
||||
@app.route('/api/users_bulk/<action>', methods=['PUT'])
|
||||
user["keycloak_groups"] = [
|
||||
g for g in user["keycloak_groups"] if not system_group(g)
|
||||
]
|
||||
return json.dumps(users), 200, {"Content-Type": "application/json"}
|
||||
|
||||
|
||||
@app.route("/api/users_bulk/<action>", methods=["PUT"])
|
||||
@login_required
|
||||
def users_bulk(action):
|
||||
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'}
|
||||
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
|
||||
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'}
|
||||
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'}
|
||||
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
|
||||
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'}
|
||||
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'}
|
||||
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
|
||||
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'}
|
||||
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'}
|
||||
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'])
|
||||
@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)
|
||||
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'}
|
||||
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({"msg": "Update password error."}),
|
||||
500,
|
||||
{"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
return json.dumps({}), 405, {"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'])
|
||||
@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':
|
||||
if request.method == "DELETE":
|
||||
app.admin.delete_user(userid)
|
||||
return json.dumps({}), 200, {'Content-Type': 'application/json'}
|
||||
if request.method == 'POST':
|
||||
data=request.get_json(force=True)
|
||||
if app.admin.get_user_username(data['username']):
|
||||
return json.dumps({'msg':'Add user error: already exists.'}), 409, {'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'}
|
||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
||||
if request.method == "POST":
|
||||
data = request.get_json(force=True)
|
||||
if app.admin.get_user_username(data["username"]):
|
||||
return (
|
||||
json.dumps({"msg": "Add user error: already exists."}),
|
||||
409,
|
||||
{"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
|
||||
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'}
|
||||
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'}
|
||||
return (
|
||||
json.dumps({"msg": "Add user error."}),
|
||||
500,
|
||||
{"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
if request.method == 'PUT':
|
||||
data=request.get_json(force=True)
|
||||
data['enabled']=True if data.get('enabled',False) else False
|
||||
data['groups']=data['groups'] if data.get('groups',False) else []
|
||||
data['roles']=[data.pop('role-keycloak')]
|
||||
if request.method == "PUT":
|
||||
data = request.get_json(force=True)
|
||||
data["enabled"] = True if data.get("enabled", False) else False
|
||||
data["groups"] = data["groups"] if data.get("groups", False) else []
|
||||
data["roles"] = [data.pop("role-keycloak")]
|
||||
try:
|
||||
app.admin.user_update(data)
|
||||
return json.dumps({}), 200, {'Content-Type': 'application/json'}
|
||||
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':
|
||||
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)
|
||||
if not user: return json.dumps({'msg':'User not found.'}), 404, {'Content-Type': 'application/json'}
|
||||
return json.dumps(user), 200, {'Content-Type': 'application/json'}
|
||||
if not user:
|
||||
return (
|
||||
json.dumps({"msg": "User not found."}),
|
||||
404,
|
||||
{"Content-Type": "application/json"},
|
||||
)
|
||||
return json.dumps(user), 200, {"Content-Type": "application/json"}
|
||||
|
||||
@app.route('/api/roles')
|
||||
|
||||
@app.route("/api/roles")
|
||||
@login_required
|
||||
def roles():
|
||||
sorted_roles = sorted(app.admin.get_roles(), key=lambda k: k['name'])
|
||||
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'}
|
||||
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'])
|
||||
|
||||
@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':
|
||||
data=request.get_json(force=True)
|
||||
data['parent']=data['parent'] if data['parent'] != '' else None
|
||||
return json.dumps(app.admin.add_group(data)), 200, {'Content-Type': 'application/json'}
|
||||
if request.method == 'DELETE':
|
||||
if request.method == "POST":
|
||||
data = request.get_json(force=True)
|
||||
data["parent"] = data["parent"] if data["parent"] != "" else None
|
||||
return (
|
||||
json.dumps(app.admin.add_group(data)),
|
||||
200,
|
||||
{"Content-Type": "application/json"},
|
||||
)
|
||||
if request.method == "DELETE":
|
||||
try:
|
||||
data=request.get_json(force=True)
|
||||
data = request.get_json(force=True)
|
||||
except:
|
||||
data=False
|
||||
|
||||
data = False
|
||||
|
||||
if data:
|
||||
res = app.admin.delete_group_by_path(data['path'])
|
||||
res = app.admin.delete_group_by_path(data["path"])
|
||||
else:
|
||||
res = app.admin.delete_group_by_id(group_id)
|
||||
return json.dumps(res), 200, {'Content-Type': 'application/json'}
|
||||
return json.dumps(res), 200, {"Content-Type": "application/json"}
|
||||
|
||||
@app.route('/api/groups')
|
||||
@app.route('/api/groups/<provider>', methods=['POST', 'PUT', 'GET', 'DELETE'])
|
||||
|
||||
@app.route("/api/groups")
|
||||
@app.route("/api/groups/<provider>", methods=["POST", "PUT", "GET", "DELETE"])
|
||||
@login_required
|
||||
def groups(provider=False):
|
||||
if request.method == 'GET':
|
||||
sorted_groups = sorted(app.admin.get_mix_groups(), key=lambda k: k['name'])
|
||||
if request.method == "GET":
|
||||
sorted_groups = sorted(app.admin.get_mix_groups(), key=lambda k: k["name"])
|
||||
if current_user.role != "admin":
|
||||
## internal groups should be avoided as are assigned with the role
|
||||
sorted_groups = [sg for sg in sorted_groups if not system_group(sg['name'])]
|
||||
sorted_groups = [sg for sg in sorted_groups if not system_group(sg["name"])]
|
||||
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'}
|
||||
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'])
|
||||
|
||||
@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'}
|
||||
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
|
||||
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':
|
||||
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'}
|
||||
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')
|
||||
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'}
|
||||
return json.dumps({}), 200, {"Content-Type": "application/json"}
|
||||
return json.dumps({}), 500, {"Content-Type": "application/json"}
|
||||
|
||||
@app.route('/api/external/users')
|
||||
|
||||
@app.route("/api/external/users")
|
||||
@login_required
|
||||
def external_users_list():
|
||||
while threads['external'] is not None and threads['external'].is_alive():
|
||||
time.sleep(.5)
|
||||
return json.dumps(app.admin.get_external_users()), 200, {'Content-Type': 'application/json'}
|
||||
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')
|
||||
|
||||
@app.route("/api/external/groups")
|
||||
@login_required
|
||||
def external_groups_list():
|
||||
while threads['external'] is not None and threads['external'].is_alive():
|
||||
time.sleep(.5)
|
||||
return json.dumps(app.admin.get_external_groups()), 200, {'Content-Type': 'application/json'}
|
||||
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'])
|
||||
|
||||
@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'}
|
||||
if request.method == "PUT":
|
||||
return (
|
||||
json.dumps(app.admin.external_roleassign(request.get_json(force=True))),
|
||||
200,
|
||||
{"Content-Type": "application/json"},
|
||||
)
|
||||
|
||||
{'groups': '/alumnes/3er',
|
||||
'firstname': 'Andreu',
|
||||
'lastname': 'B',
|
||||
'email': '12andreub@escolamontseny.cat',
|
||||
'username': '12andreub',
|
||||
'password': 'pepinillo',
|
||||
'password_temporal': 'yes',
|
||||
'role': 'student',
|
||||
'quota': '500 MB',
|
||||
'': '',
|
||||
'id': '12andreub'}
|
||||
|
||||
{
|
||||
"groups": "/alumnes/3er",
|
||||
"firstname": "Andreu",
|
||||
"lastname": "B",
|
||||
"email": "12andreub@escolamontseny.cat",
|
||||
"username": "12andreub",
|
||||
"password": "pepinillo",
|
||||
"password_temporal": "yes",
|
||||
"role": "student",
|
||||
"quota": "500 MB",
|
||||
"": "",
|
||||
"id": "12andreub",
|
||||
}
|
||||
|
||||
|
||||
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']:
|
||||
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(',')]
|
||||
user_groups = [g.strip() for g in u["groups"].split(",")]
|
||||
except:
|
||||
return {'pass':False,'msg':'User '+u['username']+' has invalid groups: '+u['groups']}
|
||||
return {
|
||||
"pass": False,
|
||||
"msg": "User " + u["username"] + " has invalid groups: " + u["groups"],
|
||||
}
|
||||
|
||||
if not re.fullmatch(email_regex, u['email']):
|
||||
return {'pass':False,'msg':'User '+u['username']+' has invalid email: '+u['email']}
|
||||
|
||||
if u['role'] not in ['admin','manager','teacher','student']:
|
||||
if u['role'] == '':
|
||||
return {'pass':False,'msg':'User '+u['username']+' has no role assigned!'}
|
||||
return {'pass':False,'msg':'User '+u['username']+' has invalid role: '+u['role']}
|
||||
|
||||
if u['password_temporal'].lower() not in ['yes','no']:
|
||||
return {'pass':False,'msg':'User '+u['username']+' has invalid password_temporal value (yes/no): '+u['password_temporal']}
|
||||
return {'pass':True,'msg':''}
|
||||
if not re.fullmatch(email_regex, u["email"]):
|
||||
return {
|
||||
"pass": False,
|
||||
"msg": "User " + u["username"] + " has invalid email: " + u["email"],
|
||||
}
|
||||
|
||||
if u["role"] not in ["admin", "manager", "teacher", "student"]:
|
||||
if u["role"] == "":
|
||||
return {
|
||||
"pass": False,
|
||||
"msg": "User " + u["username"] + " has no role assigned!",
|
||||
}
|
||||
return {
|
||||
"pass": False,
|
||||
"msg": "User " + u["username"] + " has invalid role: " + u["role"],
|
||||
}
|
||||
|
||||
if u["password_temporal"].lower() not in ["yes", "no"]:
|
||||
return {
|
||||
"pass": False,
|
||||
"msg": "User "
|
||||
+ u["username"]
|
||||
+ " has invalid password_temporal value (yes/no): "
|
||||
+ u["password_temporal"],
|
||||
}
|
||||
return {"pass": True, "msg": ""}
|
||||
|
|
|
@ -1,107 +1,136 @@
|
|||
#!flask/bin/python
|
||||
# coding=utf-8
|
||||
from admin import app
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import time,json
|
||||
import sys,os
|
||||
|
||||
from flask import request
|
||||
|
||||
from admin import app
|
||||
|
||||
from .decorators import is_internal
|
||||
|
||||
@app.route('/api/internal/users', methods=['GET'])
|
||||
|
||||
@app.route("/api/internal/users", methods=["GET"])
|
||||
@is_internal
|
||||
def internal_users():
|
||||
if request.method == 'GET':
|
||||
sorted_users = sorted(app.admin.get_mix_users(), key=lambda k: k['username'])
|
||||
if request.method == "GET":
|
||||
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']]
|
||||
users=[]
|
||||
users = []
|
||||
for user in sorted_users:
|
||||
if not user['enabled']: continue
|
||||
if not user["enabled"]:
|
||||
continue
|
||||
users.append(user_parser(user))
|
||||
return json.dumps(users), 200, {'Content-Type': 'application/json'}
|
||||
return json.dumps(users), 200, {"Content-Type": "application/json"}
|
||||
|
||||
@app.route('/api/internal/users/filter', methods=['POST'])
|
||||
|
||||
@app.route("/api/internal/users/filter", methods=["POST"])
|
||||
@is_internal
|
||||
def internal_users_search():
|
||||
if request.method == 'POST':
|
||||
data=request.get_json(force=True)
|
||||
if request.method == "POST":
|
||||
data = request.get_json(force=True)
|
||||
users = app.admin.get_mix_users()
|
||||
result = [user_parser(user) for user in filter_users(users, data['text'])]
|
||||
sorted_result = sorted(result, key=lambda k: k['id'])
|
||||
return json.dumps(sorted_result), 200, {'Content-Type': 'application/json'}
|
||||
result = [user_parser(user) for user in filter_users(users, data["text"])]
|
||||
sorted_result = sorted(result, key=lambda k: k["id"])
|
||||
return json.dumps(sorted_result), 200, {"Content-Type": "application/json"}
|
||||
|
||||
@app.route('/api/internal/groups', methods=['GET'])
|
||||
|
||||
@app.route("/api/internal/groups", methods=["GET"])
|
||||
@is_internal
|
||||
def internal_groups():
|
||||
if request.method == 'GET':
|
||||
sorted_groups = sorted(app.admin.get_mix_groups(), key=lambda k: k['name'])
|
||||
groups=[]
|
||||
if request.method == "GET":
|
||||
sorted_groups = sorted(app.admin.get_mix_groups(), key=lambda k: k["name"])
|
||||
groups = []
|
||||
for group in sorted_groups:
|
||||
if not group['path'].startswith('/'): continue
|
||||
groups.append({'id':group['path'],
|
||||
'name':group['name'],
|
||||
'description':group.get('description','')})
|
||||
return json.dumps(groups), 200, {'Content-Type': 'application/json'}
|
||||
if not group["path"].startswith("/"):
|
||||
continue
|
||||
groups.append(
|
||||
{
|
||||
"id": group["path"],
|
||||
"name": group["name"],
|
||||
"description": group.get("description", ""),
|
||||
}
|
||||
)
|
||||
return json.dumps(groups), 200, {"Content-Type": "application/json"}
|
||||
|
||||
@app.route('/api/internal/group/users', methods=['POST'])
|
||||
|
||||
@app.route("/api/internal/group/users", methods=["POST"])
|
||||
@is_internal
|
||||
def internal_group_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 request.method == "POST":
|
||||
data = request.get_json(force=True)
|
||||
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']]
|
||||
users=[]
|
||||
users = []
|
||||
for user in sorted_users:
|
||||
if data['path'] not in user['keycloak_groups'] or not user['enabled']: continue
|
||||
if data["path"] not in user["keycloak_groups"] or not user["enabled"]:
|
||||
continue
|
||||
users.append(user)
|
||||
if data.get('text',False) and data['text'] != '':
|
||||
result = [user_parser(user) for user in filter_users(users, data['text'])]
|
||||
if data.get("text", False) and data["text"] != "":
|
||||
result = [user_parser(user) for user in filter_users(users, data["text"])]
|
||||
else:
|
||||
result = [user_parser(user) for user in users]
|
||||
return json.dumps(result), 200, {'Content-Type': 'application/json'}
|
||||
return json.dumps(result), 200, {"Content-Type": "application/json"}
|
||||
|
||||
@app.route('/api/internal/roles', methods=['GET'])
|
||||
|
||||
@app.route("/api/internal/roles", methods=["GET"])
|
||||
@is_internal
|
||||
def internal_roles():
|
||||
if request.method == 'GET':
|
||||
roles=[]
|
||||
for role in sorted(app.admin.get_roles(), key=lambda k: k['name']):
|
||||
if role['name'] == 'admin': continue
|
||||
roles.append({'id':role['id'],
|
||||
'name':role['name'],
|
||||
'description':role.get('description','')})
|
||||
return json.dumps(roles), 200, {'Content-Type': 'application/json'}
|
||||
if request.method == "GET":
|
||||
roles = []
|
||||
for role in sorted(app.admin.get_roles(), key=lambda k: k["name"]):
|
||||
if role["name"] == "admin":
|
||||
continue
|
||||
roles.append(
|
||||
{
|
||||
"id": role["id"],
|
||||
"name": role["name"],
|
||||
"description": role.get("description", ""),
|
||||
}
|
||||
)
|
||||
return json.dumps(roles), 200, {"Content-Type": "application/json"}
|
||||
|
||||
@app.route('/api/internal/role/users', methods=['POST'])
|
||||
|
||||
@app.route("/api/internal/role/users", methods=["POST"])
|
||||
@is_internal
|
||||
def internal_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 request.method == "POST":
|
||||
data = request.get_json(force=True)
|
||||
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']]
|
||||
users=[]
|
||||
users = []
|
||||
for user in sorted_users:
|
||||
if data['role'] not in user['roles'] or not user['enabled']: continue
|
||||
if data["role"] not in user["roles"] or not user["enabled"]:
|
||||
continue
|
||||
users.append(user)
|
||||
if data.get('text',False) and data['text'] != '':
|
||||
result = [user_parser(user) for user in filter_users(users, data['text'])]
|
||||
if data.get("text", False) and data["text"] != "":
|
||||
result = [user_parser(user) for user in filter_users(users, data["text"])]
|
||||
else:
|
||||
result = [user_parser(user) for user in users]
|
||||
return json.dumps(result), 200, {'Content-Type': 'application/json'}
|
||||
return json.dumps(result), 200, {"Content-Type": "application/json"}
|
||||
|
||||
|
||||
def user_parser(user):
|
||||
return {'id':user['username'],
|
||||
'first':user['first'],
|
||||
'last':user['last'],
|
||||
'role':user['roles'][0] if len(user['roles']) else None,
|
||||
'email':user['email'],
|
||||
'groups':user['keycloak_groups']}
|
||||
return {
|
||||
"id": user["username"],
|
||||
"first": user["first"],
|
||||
"last": user["last"],
|
||||
"role": user["roles"][0] if len(user["roles"]) else None,
|
||||
"email": user["email"],
|
||||
"groups": user["keycloak_groups"],
|
||||
}
|
||||
|
||||
|
||||
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']]
|
||||
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"]
|
||||
]
|
||||
|
|
|
@ -1,31 +1,42 @@
|
|||
import os
|
||||
|
||||
from flask import flash, redirect, render_template, request, url_for
|
||||
from flask_login import current_user, login_required, login_user, logout_user
|
||||
|
||||
from admin import app
|
||||
from flask import render_template, flash, request, redirect, url_for
|
||||
from ..auth.authentication import *
|
||||
from flask_login import login_required, current_user, login_user, logout_user
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
from ..auth.authentication import *
|
||||
|
||||
|
||||
@app.route("/", methods=["GET", "POST"])
|
||||
@app.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
if request.form['user'] == '' or request.form['password'] == '':
|
||||
flash("Can't leave it blank",'danger')
|
||||
elif request.form['user'].startswith(' '):
|
||||
flash('Username not found or incorrect password.','warning')
|
||||
else:
|
||||
ram_user=ram_users.get(request.form['user'])
|
||||
if ram_user and request.form['password'] == ram_user['password']:
|
||||
user=User({'id': ram_user['id'], 'password': ram_user['password'], 'role': ram_user['role'], 'active': True})
|
||||
login_user(user)
|
||||
flash('Logged in successfully.','success')
|
||||
return redirect(url_for('web_users'))
|
||||
else:
|
||||
flash('Username not found or incorrect password.','warning')
|
||||
return render_template('login.html')
|
||||
if request.method == "POST":
|
||||
if request.form["user"] == "" or request.form["password"] == "":
|
||||
flash("Can't leave it blank", "danger")
|
||||
elif request.form["user"].startswith(" "):
|
||||
flash("Username not found or incorrect password.", "warning")
|
||||
else:
|
||||
ram_user = ram_users.get(request.form["user"])
|
||||
if ram_user and request.form["password"] == ram_user["password"]:
|
||||
user = User(
|
||||
{
|
||||
"id": ram_user["id"],
|
||||
"password": ram_user["password"],
|
||||
"role": ram_user["role"],
|
||||
"active": True,
|
||||
}
|
||||
)
|
||||
login_user(user)
|
||||
flash("Logged in successfully.", "success")
|
||||
return redirect(url_for("web_users"))
|
||||
else:
|
||||
flash("Username not found or incorrect password.", "warning")
|
||||
return render_template("login.html")
|
||||
|
||||
@app.route('/logout', methods=['GET'])
|
||||
|
||||
@app.route("/logout", methods=["GET"])
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('login'))
|
||||
logout_user()
|
||||
return redirect(url_for("login"))
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
# @socketio.on('connect', namespace='//sio')
|
||||
# def socketio_connect():
|
||||
# join_room('admin')
|
||||
# socketio.emit('update',
|
||||
# json.dumps('Joined'),
|
||||
# namespace='//sio',
|
||||
# socketio.emit('update',
|
||||
# json.dumps('Joined'),
|
||||
# namespace='//sio',
|
||||
# room='admin')
|
||||
|
||||
|
||||
# @socketio.on('disconnect', namespace='//sio')
|
||||
# def socketio_domains_disconnect():
|
||||
# None
|
||||
# None
|
||||
|
|
|
@ -1,23 +1,34 @@
|
|||
#!flask/bin/python
|
||||
# coding=utf-8
|
||||
from admin import app
|
||||
import logging as log
|
||||
import traceback
|
||||
|
||||
from uuid import uuid4
|
||||
import time,json
|
||||
import sys,os
|
||||
from flask import render_template, Response, request, redirect, url_for, jsonify, send_file
|
||||
import concurrent.futures
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from pprint import pprint
|
||||
from uuid import uuid4
|
||||
|
||||
from flask import (
|
||||
Response,
|
||||
jsonify,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
send_file,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import login_required
|
||||
|
||||
from admin import app
|
||||
|
||||
from ..lib.avatars import Avatars
|
||||
from .decorators import is_admin
|
||||
|
||||
from pprint import pprint
|
||||
from ..lib.avatars import Avatars
|
||||
avatars = Avatars()
|
||||
|
||||
avatars=Avatars()
|
||||
|
||||
''' OIDC TESTS '''
|
||||
""" OIDC TESTS """
|
||||
# from ..auth.authentication import oidc
|
||||
|
||||
# @app.route('/custom_callback')
|
||||
|
@ -43,47 +54,60 @@ avatars=Avatars()
|
|||
# def logoutoidc():
|
||||
# oidc.logout()
|
||||
# return 'Hi, you have been logged out! <a href="/">Return</a>'
|
||||
''' OIDC TESTS '''
|
||||
""" OIDC TESTS """
|
||||
|
||||
@app.route('/users')
|
||||
|
||||
@app.route("/users")
|
||||
@login_required
|
||||
def web_users():
|
||||
return render_template('pages/users.html', title="Users", nav="Users")
|
||||
return render_template("pages/users.html", title="Users", nav="Users")
|
||||
|
||||
@app.route('/roles')
|
||||
|
||||
@app.route("/roles")
|
||||
@login_required
|
||||
def web_roles():
|
||||
return render_template('pages/roles.html', title="Roles", nav="Roles")
|
||||
return render_template("pages/roles.html", title="Roles", nav="Roles")
|
||||
|
||||
@app.route('/groups')
|
||||
|
||||
@app.route("/groups")
|
||||
@login_required
|
||||
def web_groups(provider=False):
|
||||
return render_template('pages/groups.html', title="Groups", nav="Groups")
|
||||
return render_template("pages/groups.html", title="Groups", nav="Groups")
|
||||
|
||||
@app.route('/avatar/<userid>', methods=['GET'])
|
||||
|
||||
@app.route("/avatar/<userid>", methods=["GET"])
|
||||
@login_required
|
||||
def avatar(userid):
|
||||
if userid != 'false':
|
||||
return send_file('../avatars/master-avatars/'+userid, mimetype='image/jpeg')
|
||||
return send_file('static/img/missing.jpg', mimetype='image/jpeg')
|
||||
if userid != "false":
|
||||
return send_file("../avatars/master-avatars/" + userid, mimetype="image/jpeg")
|
||||
return send_file("static/img/missing.jpg", mimetype="image/jpeg")
|
||||
|
||||
|
||||
### SYS ADMIN
|
||||
|
||||
@app.route('/sysadmin/users')
|
||||
|
||||
@app.route("/sysadmin/users")
|
||||
@login_required
|
||||
@is_admin
|
||||
def web_sysadmin_users():
|
||||
return render_template('pages/sysadmin/users.html', title="SysAdmin Users", nav="SysAdminUsers")
|
||||
return render_template(
|
||||
"pages/sysadmin/users.html", title="SysAdmin Users", nav="SysAdminUsers"
|
||||
)
|
||||
|
||||
@app.route('/sysadmin/groups')
|
||||
|
||||
@app.route("/sysadmin/groups")
|
||||
@login_required
|
||||
@is_admin
|
||||
def web_sysadmin_groups():
|
||||
return render_template('pages/sysadmin/groups.html', title="SysAdmin Groups", nav="SysAdminGroups")
|
||||
|
||||
return render_template(
|
||||
"pages/sysadmin/groups.html", title="SysAdmin Groups", nav="SysAdminGroups"
|
||||
)
|
||||
|
||||
@app.route('/sysadmin/external')
|
||||
|
||||
@app.route("/sysadmin/external")
|
||||
@login_required
|
||||
## SysAdmin role
|
||||
def web_sysadmin_external():
|
||||
return render_template('pages/sysadmin/external.html', title="External", nav="External")
|
||||
return render_template(
|
||||
"pages/sysadmin/external.html", title="External", nav="External"
|
||||
)
|
||||
|
|
|
@ -1,25 +1,36 @@
|
|||
#!flask/bin/python
|
||||
# coding=utf-8
|
||||
|
||||
from functools import wraps
|
||||
from flask import request, redirect, url_for
|
||||
from flask_login import current_user, logout_user
|
||||
import socket
|
||||
from functools import wraps
|
||||
|
||||
from flask import redirect, request, url_for
|
||||
from flask_login import current_user, logout_user
|
||||
|
||||
|
||||
def is_admin(fn):
|
||||
@wraps(fn)
|
||||
def decorated_view(*args, **kwargs):
|
||||
if current_user.role == 'admin': return fn(*args, **kwargs)
|
||||
return redirect(url_for('login'))
|
||||
if current_user.role == "admin":
|
||||
return fn(*args, **kwargs)
|
||||
return redirect(url_for("login"))
|
||||
|
||||
return decorated_view
|
||||
|
||||
|
||||
def is_internal(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]
|
||||
remote_addr = (
|
||||
request.headers["X-Forwarded-For"].split(",")[0]
|
||||
if "X-Forwarded-For" in request.headers
|
||||
else request.remote_addr.split(",")[0]
|
||||
)
|
||||
## Now only checks if it is wordpress container,
|
||||
## but we should check if it is internal net and not haproxy
|
||||
if socket.gethostbyname('isard-apps-wordpress') == remote_addr: return fn(*args, **kwargs)
|
||||
if socket.gethostbyname("isard-apps-wordpress") == remote_addr:
|
||||
return fn(*args, **kwargs)
|
||||
logout_user()
|
||||
return redirect(url_for('login'))
|
||||
return decorated_view
|
||||
return redirect(url_for("login"))
|
||||
|
||||
return decorated_view
|
||||
|
|
|
@ -1,72 +1,89 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import time, os
|
||||
from datetime import datetime, timedelta
|
||||
import pprint
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import pprint
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import psycopg2
|
||||
import yaml
|
||||
|
||||
from admin.lib.postgres import Postgres
|
||||
from admin.lib.keycloak_client import KeycloakClient
|
||||
from admin.lib.postgres import Postgres
|
||||
|
||||
import string, random
|
||||
app = {}
|
||||
app["config"] = {}
|
||||
|
||||
app={}
|
||||
app['config']={}
|
||||
|
||||
class MoodleSaml():
|
||||
class MoodleSaml:
|
||||
def __init__(self):
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
self.pg=Postgres('isard-apps-postgresql','moodle',os.environ['MOODLE_POSTGRES_USER'],os.environ['MOODLE_POSTGRES_PASSWORD'])
|
||||
ready=True
|
||||
self.pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"moodle",
|
||||
os.environ["MOODLE_POSTGRES_USER"],
|
||||
os.environ["MOODLE_POSTGRES_PASSWORD"],
|
||||
)
|
||||
ready = True
|
||||
except:
|
||||
log.warning('Could not connect to moodle database. Retrying...')
|
||||
log.warning("Could not connect to moodle database. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Connected to moodle database.')
|
||||
log.info("Connected to moodle database.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
privatekey_pass=self.get_privatekey_pass()
|
||||
log.warning("The key: "+str(privatekey_pass))
|
||||
if privatekey_pass.endswith(os.environ['DOMAIN']):
|
||||
app['config']['MOODLE_SAML_PRIVATEKEYPASS']=privatekey_pass
|
||||
ready=True
|
||||
privatekey_pass = self.get_privatekey_pass()
|
||||
log.warning("The key: " + str(privatekey_pass))
|
||||
if privatekey_pass.endswith(os.environ["DOMAIN"]):
|
||||
app["config"]["MOODLE_SAML_PRIVATEKEYPASS"] = privatekey_pass
|
||||
ready = True
|
||||
except:
|
||||
# print(traceback.format_exc())
|
||||
log.warning('Could not get moodle site identifier. Retrying...')
|
||||
log.warning("Could not get moodle site identifier. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Got moodle site identifier.')
|
||||
log.info("Got moodle site identifier.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./moodledata/saml2/moodle."+os.environ['DOMAIN']+".crt"),"r") as crt:
|
||||
app['config']['SP_CRT']=crt.read()
|
||||
ready=True
|
||||
with open(
|
||||
os.path.join(
|
||||
"./moodledata/saml2/moodle." + os.environ["DOMAIN"] + ".crt"
|
||||
),
|
||||
"r",
|
||||
) as crt:
|
||||
app["config"]["SP_CRT"] = crt.read()
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get moodle SAML2 crt certificate. Retrying...')
|
||||
log.warning("Could not get moodle SAML2 crt certificate. Retrying...")
|
||||
time.sleep(2)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
log.info('Got moodle srt certificate.')
|
||||
log.info("Got moodle srt certificate.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./moodledata/saml2/moodle."+os.environ['DOMAIN']+".pem"),"r") as pem:
|
||||
app['config']['SP_PEM']=pem.read()
|
||||
ready=True
|
||||
with open(
|
||||
os.path.join(
|
||||
"./moodledata/saml2/moodle." + os.environ["DOMAIN"] + ".pem"
|
||||
),
|
||||
"r",
|
||||
) as pem:
|
||||
app["config"]["SP_PEM"] = pem.read()
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get moodle SAML2 pem certificate. Retrying...')
|
||||
log.warning("Could not get moodle SAML2 pem certificate. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Got moodle pem certificate.')
|
||||
log.info("Got moodle pem certificate.")
|
||||
|
||||
## This seems related to the fact that the certificate generated the first time does'nt work.
|
||||
## And when regenerating the certificate de privatekeypass seems not to be used and instead it
|
||||
|
@ -77,183 +94,259 @@ class MoodleSaml():
|
|||
## 3.- Cleanup all caches in moodle (Development tab)
|
||||
# with open(os.path.join("./moodledata/saml2/"+os.environ['MOODLE_SAML_PRIVATEKEYPASS'].replace("moodle."+os.environ['DOMAIN'],'')+'.idp.xml'),"w") as xml:
|
||||
# xml.write(self.parse_idp_metadata())
|
||||
with open(os.path.join("./moodledata/saml2/0f635d0e0f3874fff8b581c132e6c7a7.idp.xml"),"w") as xml:
|
||||
with open(
|
||||
os.path.join("./moodledata/saml2/0f635d0e0f3874fff8b581c132e6c7a7.idp.xml"),
|
||||
"w",
|
||||
) as xml:
|
||||
xml.write(self.parse_idp_metadata())
|
||||
|
||||
log.info('Written SP file on moodledata.')
|
||||
log.info("Written SP file on moodledata.")
|
||||
|
||||
try:
|
||||
self.activate_saml_plugin()
|
||||
except:
|
||||
print('Error activating saml on moodle')
|
||||
|
||||
print("Error activating saml on moodle")
|
||||
|
||||
try:
|
||||
self.set_moodle_saml_plugin()
|
||||
except:
|
||||
print('Error setting saml on moodle')
|
||||
|
||||
print("Error setting saml on moodle")
|
||||
|
||||
try:
|
||||
self.delete_keycloak_moodle_saml_plugin()
|
||||
except:
|
||||
print('Error deleting saml on keycloak')
|
||||
|
||||
print("Error deleting saml on keycloak")
|
||||
|
||||
try:
|
||||
self.add_keycloak_moodle_saml()
|
||||
except:
|
||||
print('Error adding saml on keycloak')
|
||||
print("Error adding saml on keycloak")
|
||||
|
||||
# SAML clients don't work well with composite roles so disabling and adding on realm
|
||||
# SAML clients don't work well with composite roles so disabling and adding on realm
|
||||
# self.add_client_roles()
|
||||
|
||||
def activate_saml_plugin(self):
|
||||
## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php
|
||||
return self.pg.update("""UPDATE "mdl_config" SET value = 'email,saml2' WHERE "name" = 'auth'""")
|
||||
return self.pg.update(
|
||||
"""UPDATE "mdl_config" SET value = 'email,saml2' WHERE "name" = 'auth'"""
|
||||
)
|
||||
|
||||
def get_privatekey_pass(self):
|
||||
return self.pg.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2]
|
||||
return self.pg.select(
|
||||
"""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'"""
|
||||
)[0][2]
|
||||
|
||||
def parse_idp_metadata(self):
|
||||
keycloak=KeycloakClient()
|
||||
rsa=keycloak.get_server_rsa_key()
|
||||
keycloak=None
|
||||
return '<md:EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Name="urn:keycloak"><md:EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master"><md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo><ds:KeyName>'+rsa['name']+'</ds:KeyName><ds:X509Data><ds:X509Certificate>'+rsa['certificate']+'</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml/resolve" index="0"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.'+os.environ['DOMAIN']+'/auth/realms/master/protocol/saml"/></md:IDPSSODescriptor></md:EntityDescriptor></md:EntitiesDescriptor>'
|
||||
keycloak = KeycloakClient()
|
||||
rsa = keycloak.get_server_rsa_key()
|
||||
keycloak = None
|
||||
return (
|
||||
'<md:EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Name="urn:keycloak"><md:EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master"><md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo><ds:KeyName>'
|
||||
+ rsa["name"]
|
||||
+ "</ds:KeyName><ds:X509Data><ds:X509Certificate>"
|
||||
+ rsa["certificate"]
|
||||
+ '</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master/protocol/saml/resolve" index="0"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master/protocol/saml"/><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.'
|
||||
+ os.environ["DOMAIN"]
|
||||
+ '/auth/realms/master/protocol/saml"/></md:IDPSSODescriptor></md:EntityDescriptor></md:EntitiesDescriptor>'
|
||||
)
|
||||
|
||||
def set_keycloak_moodle_saml_plugin(self):
|
||||
keycloak=KeycloakClient()
|
||||
keycloak = KeycloakClient()
|
||||
keycloak.add_moodle_client()
|
||||
keycloak=None
|
||||
keycloak = None
|
||||
|
||||
def delete_keycloak_moodle_saml_plugin(self):
|
||||
keycloak=KeycloakClient()
|
||||
keycloak.delete_client('a92d5417-92b6-4678-9cb9-51bc0edcee8c')
|
||||
keycloak=None
|
||||
keycloak = KeycloakClient()
|
||||
keycloak.delete_client("a92d5417-92b6-4678-9cb9-51bc0edcee8c")
|
||||
keycloak = None
|
||||
|
||||
def set_moodle_saml_plugin(self):
|
||||
config={'idpmetadata': self.parse_idp_metadata(),
|
||||
'certs_locked': '1',
|
||||
'duallogin': '1',
|
||||
'idpattr': 'username',
|
||||
'autocreate': '1',
|
||||
'anyauth': '1',
|
||||
'saml_role_siteadmin_map': 'admin',
|
||||
'saml_role_coursecreator_map': 'teacher',
|
||||
'saml_role_manager_map': 'manager',
|
||||
'field_map_email': 'email',
|
||||
'field_map_firstname': 'givenName',
|
||||
'field_map_lastname': 'sn'}
|
||||
config = {
|
||||
"idpmetadata": self.parse_idp_metadata(),
|
||||
"certs_locked": "1",
|
||||
"duallogin": "1",
|
||||
"idpattr": "username",
|
||||
"autocreate": "1",
|
||||
"anyauth": "1",
|
||||
"saml_role_siteadmin_map": "admin",
|
||||
"saml_role_coursecreator_map": "teacher",
|
||||
"saml_role_manager_map": "manager",
|
||||
"field_map_email": "email",
|
||||
"field_map_firstname": "givenName",
|
||||
"field_map_lastname": "sn",
|
||||
}
|
||||
for name in config.keys():
|
||||
self.pg.update("""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'auth_saml2' AND "name" = '%s'""" % (config[name],name))
|
||||
self.pg.update("""INSERT INTO "mdl_auth_saml2_idps" ("metadataurl", "entityid", "activeidp", "defaultidp", "adminidp", "defaultname", "displayname", "logo", "alias", "whitelist") VALUES
|
||||
('xml', 'https://sso.%s/auth/realms/master', 1, 0, 0, 'Login via SAML2', '', NULL, NULL, NULL);""" % (os.environ['DOMAIN']))
|
||||
self.pg.update(
|
||||
"""UPDATE "mdl_config_plugins" SET value = '%s' WHERE "plugin" = 'auth_saml2' AND "name" = '%s'"""
|
||||
% (config[name], name)
|
||||
)
|
||||
self.pg.update(
|
||||
"""INSERT INTO "mdl_auth_saml2_idps" ("metadataurl", "entityid", "activeidp", "defaultidp", "adminidp", "defaultname", "displayname", "logo", "alias", "whitelist") VALUES
|
||||
('xml', 'https://sso.%s/auth/realms/master', 1, 0, 0, 'Login via SAML2', '', NULL, NULL, NULL);"""
|
||||
% (os.environ["DOMAIN"])
|
||||
)
|
||||
|
||||
def add_keycloak_moodle_saml(self):
|
||||
client={
|
||||
"id" : "a92d5417-92b6-4678-9cb9-51bc0edcee8c",
|
||||
client = {
|
||||
"id": "a92d5417-92b6-4678-9cb9-51bc0edcee8c",
|
||||
"name": "moodle",
|
||||
"description": "moodle",
|
||||
"clientId" : "https://moodle."+os.environ['DOMAIN']+"/auth/saml2/sp/metadata.php",
|
||||
"surrogateAuthRequired" : False,
|
||||
"enabled" : True,
|
||||
"alwaysDisplayInConsole" : False,
|
||||
"clientAuthenticatorType" : "client-secret",
|
||||
"redirectUris" : [ "https://moodle."+os.environ['DOMAIN']+"/auth/saml2/sp/saml2-acs.php/moodle."+os.environ['DOMAIN']+"" ],
|
||||
"webOrigins" : [ "https://moodle."+os.environ['DOMAIN']+"" ],
|
||||
"notBefore" : 0,
|
||||
"bearerOnly" : False,
|
||||
"consentRequired" : False,
|
||||
"standardFlowEnabled" : True,
|
||||
"implicitFlowEnabled" : False,
|
||||
"directAccessGrantsEnabled" : False,
|
||||
"serviceAccountsEnabled" : False,
|
||||
"publicClient" : False,
|
||||
"frontchannelLogout" : True,
|
||||
"protocol" : "saml",
|
||||
"attributes" : {
|
||||
"saml.force.post.binding" : True,
|
||||
"saml.encrypt" : False,
|
||||
"saml_assertion_consumer_url_post" : "https://moodle."+os.environ['DOMAIN']+"/auth/saml2/sp/saml2-acs.php/moodle."+os.environ['DOMAIN']+"",
|
||||
"saml.server.signature" : True,
|
||||
"saml.server.signature.keyinfo.ext" : False,
|
||||
"saml.signing.certificate" : app['config']['SP_CRT'],
|
||||
"saml_single_logout_service_url_redirect" : "https://moodle."+os.environ['DOMAIN']+"/auth/saml2/sp/saml2-logout.php/moodle."+os.environ['DOMAIN']+"",
|
||||
"saml.signature.algorithm" : "RSA_SHA256",
|
||||
"saml_force_name_id_format" : False,
|
||||
"saml.client.signature" : True,
|
||||
"saml.encryption.certificate" : app['config']['SP_PEM'],
|
||||
"saml.authnstatement" : True,
|
||||
"saml_name_id_format" : "username",
|
||||
"saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||
"clientId": "https://moodle."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/auth/saml2/sp/metadata.php",
|
||||
"surrogateAuthRequired": False,
|
||||
"enabled": True,
|
||||
"alwaysDisplayInConsole": False,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": [
|
||||
"https://moodle."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/auth/saml2/sp/saml2-acs.php/moodle."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ ""
|
||||
],
|
||||
"webOrigins": ["https://moodle." + os.environ["DOMAIN"] + ""],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": False,
|
||||
"consentRequired": False,
|
||||
"standardFlowEnabled": True,
|
||||
"implicitFlowEnabled": False,
|
||||
"directAccessGrantsEnabled": False,
|
||||
"serviceAccountsEnabled": False,
|
||||
"publicClient": False,
|
||||
"frontchannelLogout": True,
|
||||
"protocol": "saml",
|
||||
"attributes": {
|
||||
"saml.force.post.binding": True,
|
||||
"saml.encrypt": False,
|
||||
"saml_assertion_consumer_url_post": "https://moodle."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/auth/saml2/sp/saml2-acs.php/moodle."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "",
|
||||
"saml.server.signature": True,
|
||||
"saml.server.signature.keyinfo.ext": False,
|
||||
"saml.signing.certificate": app["config"]["SP_CRT"],
|
||||
"saml_single_logout_service_url_redirect": "https://moodle."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/auth/saml2/sp/saml2-logout.php/moodle."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "",
|
||||
"saml.signature.algorithm": "RSA_SHA256",
|
||||
"saml_force_name_id_format": False,
|
||||
"saml.client.signature": True,
|
||||
"saml.encryption.certificate": app["config"]["SP_PEM"],
|
||||
"saml.authnstatement": True,
|
||||
"saml_name_id_format": "username",
|
||||
"saml_signature_canonicalization_method": "http://www.w3.org/2001/10/xml-exc-c14n#",
|
||||
},
|
||||
"authenticationFlowBindingOverrides" : { },
|
||||
"fullScopeAllowed" : True,
|
||||
"nodeReRegistrationTimeout" : -1,
|
||||
"protocolMappers" : [ {
|
||||
"id" : "9296daa3-4fc4-4b80-b007-5070f546ae13",
|
||||
"name" : "X500 sn",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-property-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
"user.attribute" : "lastName",
|
||||
"friendly.name" : "sn",
|
||||
"attribute.name" : "urn:oid:2.5.4.4"
|
||||
}
|
||||
}, {
|
||||
"id" : "ccecf6e4-d20a-4211-b67c-40200a6b2c5d",
|
||||
"name" : "username",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-property-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "Basic",
|
||||
"user.attribute" : "username",
|
||||
"friendly.name" : "username",
|
||||
"attribute.name" : "username"
|
||||
}
|
||||
}, {
|
||||
"id" : "53858403-eba2-4f6d-81d0-cced700b5719",
|
||||
"name" : "X500 givenName",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-property-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
"user.attribute" : "firstName",
|
||||
"friendly.name" : "givenName",
|
||||
"attribute.name" : "urn:oid:2.5.4.42"
|
||||
}
|
||||
}, {
|
||||
"id" : "20034db5-1d0e-4e66-b815-fb0440c6d1e2",
|
||||
"name" : "X500 email",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-property-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
"user.attribute" : "email",
|
||||
"friendly.name" : "email",
|
||||
"attribute.name" : "urn:oid:1.2.840.113549.1.9.1"
|
||||
}
|
||||
} ],
|
||||
"defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ],
|
||||
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ],
|
||||
"access" : {
|
||||
"view" : True,
|
||||
"configure" : True,
|
||||
"manage" : True
|
||||
}
|
||||
"authenticationFlowBindingOverrides": {},
|
||||
"fullScopeAllowed": True,
|
||||
"nodeReRegistrationTimeout": -1,
|
||||
"protocolMappers": [
|
||||
{
|
||||
"id": "9296daa3-4fc4-4b80-b007-5070f546ae13",
|
||||
"name": "X500 sn",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-property-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
"user.attribute": "lastName",
|
||||
"friendly.name": "sn",
|
||||
"attribute.name": "urn:oid:2.5.4.4",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "ccecf6e4-d20a-4211-b67c-40200a6b2c5d",
|
||||
"name": "username",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-property-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "Basic",
|
||||
"user.attribute": "username",
|
||||
"friendly.name": "username",
|
||||
"attribute.name": "username",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "53858403-eba2-4f6d-81d0-cced700b5719",
|
||||
"name": "X500 givenName",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-property-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
"user.attribute": "firstName",
|
||||
"friendly.name": "givenName",
|
||||
"attribute.name": "urn:oid:2.5.4.42",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "20034db5-1d0e-4e66-b815-fb0440c6d1e2",
|
||||
"name": "X500 email",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-property-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
|
||||
"user.attribute": "email",
|
||||
"friendly.name": "email",
|
||||
"attribute.name": "urn:oid:1.2.840.113549.1.9.1",
|
||||
},
|
||||
},
|
||||
],
|
||||
"defaultClientScopes": [
|
||||
"web-origins",
|
||||
"role_list",
|
||||
"roles",
|
||||
"profile",
|
||||
"email",
|
||||
],
|
||||
"optionalClientScopes": [
|
||||
"address",
|
||||
"phone",
|
||||
"offline_access",
|
||||
"microprofile-jwt",
|
||||
],
|
||||
"access": {"view": True, "configure": True, "manage": True},
|
||||
}
|
||||
keycloak=KeycloakClient()
|
||||
keycloak = KeycloakClient()
|
||||
keycloak.add_client(client)
|
||||
keycloak=None
|
||||
keycloak = None
|
||||
|
||||
def add_client_roles(self):
|
||||
keycloak=KeycloakClient()
|
||||
keycloak.add_client_role('a92d5417-92b6-4678-9cb9-51bc0edcee8c','admin','Moodle admins')
|
||||
keycloak.add_client_role('a92d5417-92b6-4678-9cb9-51bc0edcee8c','manager','Moodle managers')
|
||||
keycloak.add_client_role('a92d5417-92b6-4678-9cb9-51bc0edcee8c','teacher','Moodle teachers')
|
||||
keycloak.add_client_role('a92d5417-92b6-4678-9cb9-51bc0edcee8c','student','Moodle students')
|
||||
keycloak=None
|
||||
keycloak = KeycloakClient()
|
||||
keycloak.add_client_role(
|
||||
"a92d5417-92b6-4678-9cb9-51bc0edcee8c", "admin", "Moodle admins"
|
||||
)
|
||||
keycloak.add_client_role(
|
||||
"a92d5417-92b6-4678-9cb9-51bc0edcee8c", "manager", "Moodle managers"
|
||||
)
|
||||
keycloak.add_client_role(
|
||||
"a92d5417-92b6-4678-9cb9-51bc0edcee8c", "teacher", "Moodle teachers"
|
||||
)
|
||||
keycloak.add_client_role(
|
||||
"a92d5417-92b6-4678-9cb9-51bc0edcee8c", "student", "Moodle students"
|
||||
)
|
||||
keycloak = None
|
||||
|
||||
m=MoodleSaml()
|
||||
|
||||
m = MoodleSaml()
|
||||
|
|
|
@ -1,66 +1,81 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import time, os
|
||||
from datetime import datetime, timedelta
|
||||
import pprint
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import pprint
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import psycopg2
|
||||
import yaml
|
||||
|
||||
from admin.lib.postgres import Postgres
|
||||
from admin.lib.keycloak_client import KeycloakClient
|
||||
from admin.lib.postgres import Postgres
|
||||
|
||||
import string, random
|
||||
app = {}
|
||||
app["config"] = {}
|
||||
|
||||
app={}
|
||||
app['config']={}
|
||||
|
||||
class NextcloudSaml():
|
||||
class NextcloudSaml:
|
||||
def __init__(self):
|
||||
self.url="http://isard-sso-keycloak:8080/auth/"
|
||||
self.username=os.environ['KEYCLOAK_USER']
|
||||
self.password=os.environ['KEYCLOAK_PASSWORD']
|
||||
self.realm='master'
|
||||
self.verify=True
|
||||
self.url = "http://isard-sso-keycloak:8080/auth/"
|
||||
self.username = os.environ["KEYCLOAK_USER"]
|
||||
self.password = os.environ["KEYCLOAK_PASSWORD"]
|
||||
self.realm = "master"
|
||||
self.verify = True
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
self.pg=Postgres('isard-apps-postgresql','nextcloud',os.environ['NEXTCLOUD_POSTGRES_USER'],os.environ['NEXTCLOUD_POSTGRES_PASSWORD'])
|
||||
ready=True
|
||||
self.pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"nextcloud",
|
||||
os.environ["NEXTCLOUD_POSTGRES_USER"],
|
||||
os.environ["NEXTCLOUD_POSTGRES_PASSWORD"],
|
||||
)
|
||||
ready = True
|
||||
except:
|
||||
log.warning('Could not connect to nextcloud database. Retrying...')
|
||||
log.warning("Could not connect to nextcloud database. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Connected to nextcloud database.')
|
||||
log.info("Connected to nextcloud database.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./saml_certs/public.cert"),"r") as crt:
|
||||
app['config']['PUBLIC_CERT']=crt.read()
|
||||
ready=True
|
||||
with open(os.path.join("./saml_certs/public.cert"), "r") as crt:
|
||||
app["config"]["PUBLIC_CERT"] = crt.read()
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get public certificate to be used in nextcloud. Retrying...')
|
||||
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
|
||||
log.warning(
|
||||
"Could not get public certificate to be used in nextcloud. Retrying..."
|
||||
)
|
||||
log.warning(
|
||||
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
|
||||
)
|
||||
time.sleep(2)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
log.info('Got moodle srt certificate to be used in nextcloud.')
|
||||
log.info("Got moodle srt certificate to be used in nextcloud.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./saml_certs/private.key"),"r") as pem:
|
||||
app['config']['PRIVATE_KEY']=pem.read()
|
||||
ready=True
|
||||
with open(os.path.join("./saml_certs/private.key"), "r") as pem:
|
||||
app["config"]["PRIVATE_KEY"] = pem.read()
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get private key to be used in nextcloud. Retrying...')
|
||||
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
|
||||
log.warning(
|
||||
"Could not get private key to be used in nextcloud. Retrying..."
|
||||
)
|
||||
log.warning(
|
||||
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
|
||||
)
|
||||
time.sleep(2)
|
||||
log.info('Got moodle pem certificate to be used in nextcloud.')
|
||||
log.info("Got moodle pem certificate to be used in nextcloud.")
|
||||
|
||||
# ## This seems related to the fact that the certificate generated the first time does'nt work.
|
||||
# ## And when regenerating the certificate de privatekeypass seems not to be used and instead it
|
||||
|
@ -77,30 +92,32 @@ class NextcloudSaml():
|
|||
try:
|
||||
self.reset_saml()
|
||||
except:
|
||||
print('Error resetting saml on nextcloud')
|
||||
|
||||
print("Error resetting saml on nextcloud")
|
||||
|
||||
try:
|
||||
self.delete_keycloak_nextcloud_saml_plugin()
|
||||
except:
|
||||
print('Error resetting saml on keycloak')
|
||||
|
||||
print("Error resetting saml on keycloak")
|
||||
|
||||
try:
|
||||
self.set_nextcloud_saml_plugin()
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
print('Error adding saml on nextcloud')
|
||||
|
||||
print("Error adding saml on nextcloud")
|
||||
|
||||
try:
|
||||
self.add_keycloak_nextcloud_saml()
|
||||
except:
|
||||
print('Error adding saml on keycloak')
|
||||
print("Error adding saml on keycloak")
|
||||
|
||||
def connect(self):
|
||||
self.keycloak= KeycloakClient(url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm=self.realm,
|
||||
verify=self.verify)
|
||||
self.keycloak = KeycloakClient(
|
||||
url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm=self.realm,
|
||||
verify=self.verify,
|
||||
)
|
||||
|
||||
# def activate_saml_plugin(self):
|
||||
# ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php
|
||||
|
@ -111,22 +128,23 @@ class NextcloudSaml():
|
|||
|
||||
def parse_idp_cert(self):
|
||||
self.connect()
|
||||
rsa=self.keycloak.get_server_rsa_key()
|
||||
self.keycloak=None
|
||||
return rsa['certificate']
|
||||
rsa = self.keycloak.get_server_rsa_key()
|
||||
self.keycloak = None
|
||||
return rsa["certificate"]
|
||||
|
||||
def set_keycloak_nextcloud_saml_plugin(self):
|
||||
self.connect()
|
||||
self.keycloak.add_nextcloud_client()
|
||||
self.keycloak=None
|
||||
self.keycloak = None
|
||||
|
||||
def delete_keycloak_nextcloud_saml_plugin(self):
|
||||
self.connect()
|
||||
self.keycloak.delete_client('bef873f0-2079-4876-8657-067de27d01b7')
|
||||
self.keycloak=None
|
||||
self.keycloak.delete_client("bef873f0-2079-4876-8657-067de27d01b7")
|
||||
self.keycloak = None
|
||||
|
||||
def set_nextcloud_saml_plugin(self):
|
||||
self.pg.update("""INSERT INTO "oc_appconfig" ("appid", "configkey", "configvalue") VALUES
|
||||
self.pg.update(
|
||||
"""INSERT INTO "oc_appconfig" ("appid", "configkey", "configvalue") VALUES
|
||||
('user_saml', 'general-uid_mapping', 'username'),
|
||||
('user_saml', 'type', 'saml'),
|
||||
('user_saml', 'sp-privateKey', '%s'),
|
||||
|
@ -144,155 +162,192 @@ class NextcloudSaml():
|
|||
('user_saml', 'security-wantAssertionsSigned', '1'),
|
||||
('user_saml', 'general-idp0_display_name', 'SAML Login'),
|
||||
('user_saml', 'sp-x509cert', '%s'),
|
||||
('user_saml', 'idp-singleLogoutService.url', 'https://sso.%s/auth/realms/master/protocol/saml');""" % (app['config']['PRIVATE_KEY'],os.environ['DOMAIN'],os.environ['DOMAIN'],self.parse_idp_cert(),app['config']['PUBLIC_CERT'],os.environ['DOMAIN']))
|
||||
|
||||
('user_saml', 'idp-singleLogoutService.url', 'https://sso.%s/auth/realms/master/protocol/saml');"""
|
||||
% (
|
||||
app["config"]["PRIVATE_KEY"],
|
||||
os.environ["DOMAIN"],
|
||||
os.environ["DOMAIN"],
|
||||
self.parse_idp_cert(),
|
||||
app["config"]["PUBLIC_CERT"],
|
||||
os.environ["DOMAIN"],
|
||||
)
|
||||
)
|
||||
|
||||
def reset_saml(self):
|
||||
cfg_list=['general-uid_mapping',
|
||||
'sp-privateKey',
|
||||
'saml-attribute-mapping-email_mapping',
|
||||
'saml-attribute-mapping-quota_mapping',
|
||||
'saml-attribute-mapping-displayName_mapping',
|
||||
'saml-attribute-mapping-group_mapping',
|
||||
'idp-entityId',
|
||||
'idp-singleSignOnService.url',
|
||||
'idp-x509cert',
|
||||
'security-authnRequestsSigned',
|
||||
'security-logoutRequestSigned',
|
||||
'security-logoutResponseSigned',
|
||||
'security-wantMessagesSigned',
|
||||
'security-wantAssertionsSigned',
|
||||
'general-idp0_display_name',
|
||||
'type',
|
||||
'sp-x509cert',
|
||||
'idp-singleLogoutService.url']
|
||||
cfg_list = [
|
||||
"general-uid_mapping",
|
||||
"sp-privateKey",
|
||||
"saml-attribute-mapping-email_mapping",
|
||||
"saml-attribute-mapping-quota_mapping",
|
||||
"saml-attribute-mapping-displayName_mapping",
|
||||
"saml-attribute-mapping-group_mapping",
|
||||
"idp-entityId",
|
||||
"idp-singleSignOnService.url",
|
||||
"idp-x509cert",
|
||||
"security-authnRequestsSigned",
|
||||
"security-logoutRequestSigned",
|
||||
"security-logoutResponseSigned",
|
||||
"security-wantMessagesSigned",
|
||||
"security-wantAssertionsSigned",
|
||||
"general-idp0_display_name",
|
||||
"type",
|
||||
"sp-x509cert",
|
||||
"idp-singleLogoutService.url",
|
||||
]
|
||||
for cfg in cfg_list:
|
||||
self.pg.update("""DELETE FROM "oc_appconfig" WHERE appid = 'user_saml' AND configkey = '%s'""" % (cfg))
|
||||
self.pg.update(
|
||||
"""DELETE FROM "oc_appconfig" WHERE appid = 'user_saml' AND configkey = '%s'"""
|
||||
% (cfg)
|
||||
)
|
||||
|
||||
def add_keycloak_nextcloud_saml(self):
|
||||
client={"id" : "bef873f0-2079-4876-8657-067de27d01b7",
|
||||
client = {
|
||||
"id": "bef873f0-2079-4876-8657-067de27d01b7",
|
||||
"name": "nextcloud",
|
||||
"description": "nextcloud",
|
||||
"clientId" : "https://nextcloud."+os.environ['DOMAIN']+"/apps/user_saml/saml/metadata",
|
||||
"surrogateAuthRequired" : False,
|
||||
"enabled" : True,
|
||||
"alwaysDisplayInConsole" : False,
|
||||
"clientAuthenticatorType" : "client-secret",
|
||||
"redirectUris" : [ "https://nextcloud."+os.environ['DOMAIN']+"/apps/user_saml/saml/acs" ],
|
||||
"webOrigins" : [ "https://nextcloud."+os.environ['DOMAIN'] ],
|
||||
"notBefore" : 0,
|
||||
"bearerOnly" : False,
|
||||
"consentRequired" : False,
|
||||
"standardFlowEnabled" : True,
|
||||
"implicitFlowEnabled" : False,
|
||||
"directAccessGrantsEnabled" : False,
|
||||
"serviceAccountsEnabled" : False,
|
||||
"publicClient" : False,
|
||||
"frontchannelLogout" : True,
|
||||
"protocol" : "saml",
|
||||
"attributes" : {
|
||||
"saml.assertion.signature" : True,
|
||||
"saml.force.post.binding" : True,
|
||||
"saml_assertion_consumer_url_post" : "https://nextcloud."+os.environ['DOMAIN']+"/apps/user_saml/saml/acs",
|
||||
"saml.server.signature" : True,
|
||||
"saml.server.signature.keyinfo.ext" : False,
|
||||
"saml.signing.certificate" : app['config']['PUBLIC_CERT'],
|
||||
"saml_single_logout_service_url_redirect" : "https://nextcloud."+os.environ['DOMAIN']+"/apps/user_saml/saml/sls",
|
||||
"saml.signature.algorithm" : "RSA_SHA256",
|
||||
"saml_force_name_id_format" : False,
|
||||
"saml.client.signature" : False,
|
||||
"saml.authnstatement" : True,
|
||||
"saml_name_id_format" : "username",
|
||||
"saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||
"clientId": "https://nextcloud."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/apps/user_saml/saml/metadata",
|
||||
"surrogateAuthRequired": False,
|
||||
"enabled": True,
|
||||
"alwaysDisplayInConsole": False,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": [
|
||||
"https://nextcloud." + os.environ["DOMAIN"] + "/apps/user_saml/saml/acs"
|
||||
],
|
||||
"webOrigins": ["https://nextcloud." + os.environ["DOMAIN"]],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": False,
|
||||
"consentRequired": False,
|
||||
"standardFlowEnabled": True,
|
||||
"implicitFlowEnabled": False,
|
||||
"directAccessGrantsEnabled": False,
|
||||
"serviceAccountsEnabled": False,
|
||||
"publicClient": False,
|
||||
"frontchannelLogout": True,
|
||||
"protocol": "saml",
|
||||
"attributes": {
|
||||
"saml.assertion.signature": True,
|
||||
"saml.force.post.binding": True,
|
||||
"saml_assertion_consumer_url_post": "https://nextcloud."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/apps/user_saml/saml/acs",
|
||||
"saml.server.signature": True,
|
||||
"saml.server.signature.keyinfo.ext": False,
|
||||
"saml.signing.certificate": app["config"]["PUBLIC_CERT"],
|
||||
"saml_single_logout_service_url_redirect": "https://nextcloud."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/apps/user_saml/saml/sls",
|
||||
"saml.signature.algorithm": "RSA_SHA256",
|
||||
"saml_force_name_id_format": False,
|
||||
"saml.client.signature": False,
|
||||
"saml.authnstatement": True,
|
||||
"saml_name_id_format": "username",
|
||||
"saml_signature_canonicalization_method": "http://www.w3.org/2001/10/xml-exc-c14n#",
|
||||
},
|
||||
"authenticationFlowBindingOverrides" : { },
|
||||
"fullScopeAllowed" : True,
|
||||
"nodeReRegistrationTimeout" : -1,
|
||||
"protocolMappers" : [ {
|
||||
"id" : "e8e4acff-da2b-46aa-8bdb-ba42171671d6",
|
||||
"name" : "username",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-attribute-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "Basic",
|
||||
"user.attribute" : "username",
|
||||
"friendly.name" : "username",
|
||||
"attribute.name" : "username"
|
||||
}
|
||||
}, {
|
||||
"id" : "8ab13cd7-822a-40d5-a1e1-9f556aed2332",
|
||||
"name" : "quota",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-attribute-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "Basic",
|
||||
"user.attribute" : "quota",
|
||||
"friendly.name" : "quota",
|
||||
"attribute.name" : "quota"
|
||||
}
|
||||
}, {
|
||||
"id" : "28206b59-757b-4e3c-81cb-0b6053b1fd3d",
|
||||
"name" : "email",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-property-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "Basic",
|
||||
"user.attribute" : "email",
|
||||
"friendly.name" : "email",
|
||||
"attribute.name" : "email"
|
||||
}
|
||||
}, {
|
||||
"id" : "5176a593-180f-4924-b294-b83a0d8d5972",
|
||||
"name" : "displayname",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-javascript-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"single" : False,
|
||||
"Script" : "/**\n * Available variables: \n * user - the current user\n * realm - the current realm\n * clientSession - the current clientSession\n * userSession - the current userSession\n * keycloakSession - the current keycloakSession\n */\n\n\n//insert your code here...\nvar Output = user.getFirstName()+\" \"+user.getLastName();\nOutput;\n",
|
||||
"attribute.nameformat" : "Basic",
|
||||
"friendly.name" : "displayname",
|
||||
"attribute.name" : "displayname"
|
||||
}
|
||||
}, {
|
||||
"id" : "e51e04b9-f71a-42de-819e-dd9285246ada",
|
||||
"name" : "Roles",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-role-list-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"single" : True,
|
||||
"attribute.nameformat" : "Basic",
|
||||
"friendly.name" : "Roles",
|
||||
"attribute.name" : "Roles"
|
||||
}
|
||||
}, {
|
||||
"id" : "9c101249-bb09-4cc8-8f75-5a18fcb307e6",
|
||||
"name" : "group_list",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-group-membership-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"single" : True,
|
||||
"attribute.nameformat" : "Basic",
|
||||
"full.path" : False,
|
||||
"friendly.name" : "member",
|
||||
"attribute.name" : "member"
|
||||
}
|
||||
} ],
|
||||
"defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ],
|
||||
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ],
|
||||
"access" : {
|
||||
"view" : True,
|
||||
"configure" : True,
|
||||
"manage" : True
|
||||
}
|
||||
"authenticationFlowBindingOverrides": {},
|
||||
"fullScopeAllowed": True,
|
||||
"nodeReRegistrationTimeout": -1,
|
||||
"protocolMappers": [
|
||||
{
|
||||
"id": "e8e4acff-da2b-46aa-8bdb-ba42171671d6",
|
||||
"name": "username",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-attribute-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "Basic",
|
||||
"user.attribute": "username",
|
||||
"friendly.name": "username",
|
||||
"attribute.name": "username",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "8ab13cd7-822a-40d5-a1e1-9f556aed2332",
|
||||
"name": "quota",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-attribute-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "Basic",
|
||||
"user.attribute": "quota",
|
||||
"friendly.name": "quota",
|
||||
"attribute.name": "quota",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "28206b59-757b-4e3c-81cb-0b6053b1fd3d",
|
||||
"name": "email",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-property-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "Basic",
|
||||
"user.attribute": "email",
|
||||
"friendly.name": "email",
|
||||
"attribute.name": "email",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "5176a593-180f-4924-b294-b83a0d8d5972",
|
||||
"name": "displayname",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-javascript-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"single": False,
|
||||
"Script": '/**\n * Available variables: \n * user - the current user\n * realm - the current realm\n * clientSession - the current clientSession\n * userSession - the current userSession\n * keycloakSession - the current keycloakSession\n */\n\n\n//insert your code here...\nvar Output = user.getFirstName()+" "+user.getLastName();\nOutput;\n',
|
||||
"attribute.nameformat": "Basic",
|
||||
"friendly.name": "displayname",
|
||||
"attribute.name": "displayname",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "e51e04b9-f71a-42de-819e-dd9285246ada",
|
||||
"name": "Roles",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-role-list-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"single": True,
|
||||
"attribute.nameformat": "Basic",
|
||||
"friendly.name": "Roles",
|
||||
"attribute.name": "Roles",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "9c101249-bb09-4cc8-8f75-5a18fcb307e6",
|
||||
"name": "group_list",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-group-membership-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"single": True,
|
||||
"attribute.nameformat": "Basic",
|
||||
"full.path": False,
|
||||
"friendly.name": "member",
|
||||
"attribute.name": "member",
|
||||
},
|
||||
},
|
||||
],
|
||||
"defaultClientScopes": [
|
||||
"web-origins",
|
||||
"role_list",
|
||||
"roles",
|
||||
"profile",
|
||||
"email",
|
||||
],
|
||||
"optionalClientScopes": [
|
||||
"address",
|
||||
"phone",
|
||||
"offline_access",
|
||||
"microprofile-jwt",
|
||||
],
|
||||
"access": {"view": True, "configure": True, "manage": True},
|
||||
}
|
||||
self.connect()
|
||||
self.keycloak.add_client(client)
|
||||
self.keycloak=None
|
||||
self.keycloak = None
|
||||
|
||||
n=NextcloudSaml()
|
||||
|
||||
n = NextcloudSaml()
|
||||
|
|
|
@ -1,66 +1,81 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import time, os
|
||||
from datetime import datetime, timedelta
|
||||
import pprint
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import pprint
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import psycopg2
|
||||
import yaml
|
||||
|
||||
from admin.lib.postgres import Postgres
|
||||
from admin.lib.keycloak_client import KeycloakClient
|
||||
from admin.lib.postgres import Postgres
|
||||
|
||||
import string, random
|
||||
app = {}
|
||||
app["config"] = {}
|
||||
|
||||
app={}
|
||||
app['config']={}
|
||||
|
||||
class NextcloudSaml():
|
||||
class NextcloudSaml:
|
||||
def __init__(self):
|
||||
self.url="http://isard-sso-keycloak:8080/auth/"
|
||||
self.username=os.environ['KEYCLOAK_USER']
|
||||
self.password=os.environ['KEYCLOAK_PASSWORD']
|
||||
self.realm='master'
|
||||
self.verify=True
|
||||
self.url = "http://isard-sso-keycloak:8080/auth/"
|
||||
self.username = os.environ["KEYCLOAK_USER"]
|
||||
self.password = os.environ["KEYCLOAK_PASSWORD"]
|
||||
self.realm = "master"
|
||||
self.verify = True
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
self.pg=Postgres('isard-apps-postgresql','nextcloud',os.environ['NEXTCLOUD_POSTGRES_USER'],os.environ['NEXTCLOUD_POSTGRES_PASSWORD'])
|
||||
ready=True
|
||||
self.pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"nextcloud",
|
||||
os.environ["NEXTCLOUD_POSTGRES_USER"],
|
||||
os.environ["NEXTCLOUD_POSTGRES_PASSWORD"],
|
||||
)
|
||||
ready = True
|
||||
except:
|
||||
log.warning('Could not connect to nextcloud database. Retrying...')
|
||||
log.warning("Could not connect to nextcloud database. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Connected to nextcloud database.')
|
||||
log.info("Connected to nextcloud database.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./saml_certs/public.cert"),"r") as crt:
|
||||
app['config']['PUBLIC_CERT']=crt.read()
|
||||
ready=True
|
||||
with open(os.path.join("./saml_certs/public.cert"), "r") as crt:
|
||||
app["config"]["PUBLIC_CERT"] = crt.read()
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get public certificate to be used in nextcloud. Retrying...')
|
||||
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
|
||||
log.warning(
|
||||
"Could not get public certificate to be used in nextcloud. Retrying..."
|
||||
)
|
||||
log.warning(
|
||||
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
|
||||
)
|
||||
time.sleep(2)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
log.info('Got moodle srt certificate to be used in nextcloud.')
|
||||
log.info("Got moodle srt certificate to be used in nextcloud.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./saml_certs/private.key"),"r") as pem:
|
||||
app['config']['PRIVATE_KEY']=pem.read()
|
||||
ready=True
|
||||
with open(os.path.join("./saml_certs/private.key"), "r") as pem:
|
||||
app["config"]["PRIVATE_KEY"] = pem.read()
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get private key to be used in nextcloud. Retrying...')
|
||||
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
|
||||
log.warning(
|
||||
"Could not get private key to be used in nextcloud. Retrying..."
|
||||
)
|
||||
log.warning(
|
||||
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
|
||||
)
|
||||
time.sleep(2)
|
||||
log.info('Got moodle pem certificate to be used in nextcloud.')
|
||||
log.info("Got moodle pem certificate to be used in nextcloud.")
|
||||
|
||||
# ## This seems related to the fact that the certificate generated the first time does'nt work.
|
||||
# ## And when regenerating the certificate de privatekeypass seems not to be used and instead it
|
||||
|
@ -77,20 +92,22 @@ class NextcloudSaml():
|
|||
try:
|
||||
self.reset_saml_certs()
|
||||
except:
|
||||
print('Error resetting saml on nextcloud')
|
||||
print("Error resetting saml on nextcloud")
|
||||
|
||||
try:
|
||||
self.set_nextcloud_saml_plugin_certs()
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
print('Error adding saml on nextcloud')
|
||||
print("Error adding saml on nextcloud")
|
||||
|
||||
def connect(self):
|
||||
self.keycloak= KeycloakClient(url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm=self.realm,
|
||||
verify=self.verify)
|
||||
self.keycloak = KeycloakClient(
|
||||
url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm=self.realm,
|
||||
verify=self.verify,
|
||||
)
|
||||
|
||||
# def activate_saml_plugin(self):
|
||||
# ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php
|
||||
|
@ -101,22 +118,30 @@ class NextcloudSaml():
|
|||
|
||||
def parse_idp_cert(self):
|
||||
self.connect()
|
||||
rsa=self.keycloak.get_server_rsa_key()
|
||||
self.keycloak=None
|
||||
return rsa['certificate']
|
||||
rsa = self.keycloak.get_server_rsa_key()
|
||||
self.keycloak = None
|
||||
return rsa["certificate"]
|
||||
|
||||
def set_nextcloud_saml_plugin_certs(self):
|
||||
self.pg.update("""INSERT INTO "oc_appconfig" ("appid", "configkey", "configvalue") VALUES
|
||||
self.pg.update(
|
||||
"""INSERT INTO "oc_appconfig" ("appid", "configkey", "configvalue") VALUES
|
||||
('user_saml', 'sp-privateKey', '%s'),
|
||||
('user_saml', 'idp-x509cert', '%s'),
|
||||
('user_saml', 'sp-x509cert', '%s');""" % (app['config']['PRIVATE_KEY'],self.parse_idp_cert(),app['config']['PUBLIC_CERT']))
|
||||
|
||||
('user_saml', 'sp-x509cert', '%s');"""
|
||||
% (
|
||||
app["config"]["PRIVATE_KEY"],
|
||||
self.parse_idp_cert(),
|
||||
app["config"]["PUBLIC_CERT"],
|
||||
)
|
||||
)
|
||||
|
||||
def reset_saml_certs(self):
|
||||
cfg_list=['sp-privateKey',
|
||||
'idp-x509cert',
|
||||
'sp-x509cert']
|
||||
cfg_list = ["sp-privateKey", "idp-x509cert", "sp-x509cert"]
|
||||
for cfg in cfg_list:
|
||||
self.pg.update("""DELETE FROM "oc_appconfig" WHERE appid = 'user_saml' AND configkey = '%s'""" % (cfg))
|
||||
self.pg.update(
|
||||
"""DELETE FROM "oc_appconfig" WHERE appid = 'user_saml' AND configkey = '%s'"""
|
||||
% (cfg)
|
||||
)
|
||||
|
||||
n=NextcloudSaml()
|
||||
|
||||
n = NextcloudSaml()
|
||||
|
|
|
@ -1,50 +1,47 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import pprint
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import pprint
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import psycopg2
|
||||
import yaml
|
||||
|
||||
class Postgres():
|
||||
|
||||
def __init__(self,host,database,user,password):
|
||||
class Postgres:
|
||||
def __init__(self, host, database, user, password):
|
||||
self.conn = psycopg2.connect(
|
||||
host=host,
|
||||
database=database,
|
||||
user=user,
|
||||
password=password)
|
||||
|
||||
host=host, database=database, user=user, password=password
|
||||
)
|
||||
|
||||
# def __del__(self):
|
||||
# self.cur.close()
|
||||
# self.conn.close()
|
||||
|
||||
def select(self,sql):
|
||||
def select(self, sql):
|
||||
self.cur = self.conn.cursor()
|
||||
self.cur.execute(sql)
|
||||
data=self.cur.fetchall()
|
||||
data = self.cur.fetchall()
|
||||
self.cur.close()
|
||||
return data
|
||||
|
||||
def update(self,sql):
|
||||
def update(self, sql):
|
||||
self.cur = self.conn.cursor()
|
||||
self.cur.execute(sql)
|
||||
self.conn.commit()
|
||||
self.cur.close()
|
||||
# return self.cur.fetchall()
|
||||
|
||||
def select_with_headers(self,sql):
|
||||
def select_with_headers(self, sql):
|
||||
self.cur = self.conn.cursor()
|
||||
self.cur.execute(sql)
|
||||
data=self.cur.fetchall()
|
||||
data = self.cur.fetchall()
|
||||
fields = [a.name for a in self.cur.description]
|
||||
self.cur.close()
|
||||
return (fields,data)
|
||||
return (fields, data)
|
||||
|
||||
# def update_moodle_saml_plugin(self):
|
||||
# plugin[('idpmetadata', '<md:EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Name="urn:keycloak"><md:EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://sso.'+app.config['DOMAIN']+'/auth/realms/master"><md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo><ds:KeyName>NrtA5ynG0htowP3SXw7dBJRIAMxn-1PwuuXwOwNhlRw</ds:KeyName><ds:X509Data><ds:X509Certificate>MIICmzCCAYMCBgF5jb0RCTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjEwNTIxMDcwMjI4WhcNMzEwNTIxMDcwNDA4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCI8xh/C0+frz3kgWiUbziTDls71R2YiXLSVE+bw7gbEgZUGCLhoEI679azMtIxmnzM/snIX+yTb12+XoYkgbiLTMPQfnH+Kiab6g3HL3KPfhqS+yWkFxOoCp6Ibmp7yPlVWuHH+MBfO8OBr/r8Ao7heFbuzjiLd1KG67rcoaxfDgMuBoEomg1bgEjFgHaQIrSC6OZzH0h987/arqufZXeXlfyiqScMPUi+u5IpDWSwz06UKP0k8mxzNSlpZ93CKOUSsV0SMLxqg7FQ3SGiOk577bGW9o9BDTkkmSo3Up6smc0LzwvvUwuNd0B1irGkWZFQN9OXJnJYf1InEebIMtmPAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADM34+qEGeBQ22luphVTuVJtGxcbxLx7DfsT0QfJD/OuxTTbNAa1VRyarb5juIAkqdj4y2quZna9ZXLecVo4RkwpzPoKoAkYA8b+kHnWqEwJi9iPrDvKb+GR0bBkLPN49YxIZ8IdKX/PRa3yuLHe+loiNsCaS/2ZK2KO46COsqU4QX1iVhF9kWphNLybjNAX45B6cJLsa1g0vXLdm3kv3SB4I2fErFVaOoDtFIjttoYlXdpUiThkPXBfr7N67P3dZHaS4tjJh+IZ8I6TINpcsH8dBkUhzYEIPHCePwSiC1w6WDBLNDuKt1mj1CZrLq+1x+Yhrs+QNRheEKGi89HZ8N0=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml/resolve" index="0"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://sso.mydomain.duckdns.org/auth/realms/master/protocol/saml"/></md:IDPSSODescriptor></md:EntityDescriptor></md:EntitiesDescriptor>')]
|
||||
|
@ -52,4 +49,4 @@ class Postgres():
|
|||
# cursor.execute(pg_update, (title, bookid))
|
||||
# connection.commit()
|
||||
# count = cursor.rowcount
|
||||
# print(count, "Successfully Updated!")
|
||||
# print(count, "Successfully Updated!")
|
||||
|
|
|
@ -1,64 +1,82 @@
|
|||
#!/usr/bin/env python
|
||||
import time ,os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
from pprint import pprint
|
||||
|
||||
import yaml
|
||||
from keycloak import KeycloakAdmin
|
||||
from postgres import Postgres
|
||||
|
||||
from minio import Minio
|
||||
from minio.commonconfig import REPLACE, CopySource
|
||||
from minio.deleteobjects import DeleteObject
|
||||
|
||||
class DefaultAvatars():
|
||||
def __init__(self,
|
||||
url="http://isard-sso-keycloak:8080/auth/",
|
||||
username=os.environ['KEYCLOAK_USER'],
|
||||
password=os.environ['KEYCLOAK_PASSWORD'],
|
||||
realm='master',
|
||||
verify=True):
|
||||
self.url=url
|
||||
self.username=username
|
||||
self.password=password
|
||||
self.realm=realm
|
||||
self.verify=verify
|
||||
from postgres import Postgres
|
||||
|
||||
self.keycloak_pg=Postgres('isard-apps-postgresql','keycloak',os.environ['KEYCLOAK_DB_USER'],os.environ['KEYCLOAK_DB_PASSWORD'])
|
||||
|
||||
class DefaultAvatars:
|
||||
def __init__(
|
||||
self,
|
||||
url="http://isard-sso-keycloak:8080/auth/",
|
||||
username=os.environ["KEYCLOAK_USER"],
|
||||
password=os.environ["KEYCLOAK_PASSWORD"],
|
||||
realm="master",
|
||||
verify=True,
|
||||
):
|
||||
self.url = url
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.realm = realm
|
||||
self.verify = verify
|
||||
|
||||
self.keycloak_pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"keycloak",
|
||||
os.environ["KEYCLOAK_DB_USER"],
|
||||
os.environ["KEYCLOAK_DB_PASSWORD"],
|
||||
)
|
||||
|
||||
self.mclient = Minio(
|
||||
"isard-sso-avatars:9000",
|
||||
access_key="AKIAIOSFODNN7EXAMPLE",
|
||||
secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
secure=False
|
||||
)
|
||||
self.bucket='master-avatars'
|
||||
"isard-sso-avatars:9000",
|
||||
access_key="AKIAIOSFODNN7EXAMPLE",
|
||||
secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
||||
secure=False,
|
||||
)
|
||||
self.bucket = "master-avatars"
|
||||
self._minio_set_realm()
|
||||
self.update_missing_avatars()
|
||||
|
||||
def connect(self):
|
||||
self.keycloak_admin = KeycloakAdmin(server_url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm_name=self.realm,
|
||||
verify=self.verify)
|
||||
self.keycloak_admin = KeycloakAdmin(
|
||||
server_url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm_name=self.realm,
|
||||
verify=self.verify,
|
||||
)
|
||||
|
||||
def update_missing_avatars(self):
|
||||
sys_roles=['admin','manager','teacher','student']
|
||||
sys_roles = ["admin", "manager", "teacher", "student"]
|
||||
for u in self.get_users_without_image():
|
||||
try:
|
||||
img=[r+'.jpg' for r in sys_roles if r in u['role']][0]
|
||||
img = [r + ".jpg" for r in sys_roles if r in u["role"]][0]
|
||||
except:
|
||||
img='unknown.jpg'
|
||||
img = "unknown.jpg"
|
||||
|
||||
self.mclient.fput_object(
|
||||
self.bucket, u['id'], "custom/avatars/"+img,
|
||||
content_type="image/jpeg ",
|
||||
self.bucket,
|
||||
u["id"],
|
||||
"custom/avatars/" + img,
|
||||
content_type="image/jpeg ",
|
||||
)
|
||||
log.warning(
|
||||
" AVATARS: Updated avatar for user "
|
||||
+ u["username"]
|
||||
+ " with role "
|
||||
+ img.split(".")[0]
|
||||
)
|
||||
log.warning(' AVATARS: Updated avatar for user '+u['username']+' with role '+img.split('.')[0])
|
||||
|
||||
def _minio_set_realm(self):
|
||||
if not self.mclient.bucket_exists(self.bucket):
|
||||
|
@ -72,17 +90,17 @@ class DefaultAvatars():
|
|||
lambda x: DeleteObject(x.object_name),
|
||||
self.mclient.list_objects(self.bucket),
|
||||
)
|
||||
errors=self.mclient.remove_objects(self.bucket, delete_object_list)
|
||||
errors = self.mclient.remove_objects(self.bucket, delete_object_list)
|
||||
for error in errors:
|
||||
log.error(" AVATARS: Error occured when deleting avatar object", error)
|
||||
|
||||
def get_users(self):
|
||||
self.connect()
|
||||
users=self.get_users_with_groups_and_roles()
|
||||
users = self.get_users_with_groups_and_roles()
|
||||
return users
|
||||
|
||||
def get_users_without_image(self):
|
||||
return [u for u in self.get_users() if u['id'] not in self.minio_get_objects()]
|
||||
return [u for u in self.get_users() if u["id"] not in self.minio_get_objects()]
|
||||
|
||||
def get_users_with_groups_and_roles(self):
|
||||
q = """select u.id, u.username, u.email, u.first_name, u.last_name, u.realm_id, ua.value as quota
|
||||
|
@ -99,20 +117,28 @@ class DefaultAvatars():
|
|||
group by u.id,u.username,u.email,u.first_name,u.last_name, u.realm_id, ua.value
|
||||
order by u.username"""
|
||||
|
||||
(headers,users)=self.keycloak_pg.select_with_headers(q)
|
||||
(headers, users) = self.keycloak_pg.select_with_headers(q)
|
||||
|
||||
users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-3]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-2]))]) +\
|
||||
([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users]
|
||||
|
||||
users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-3]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-2]))]) +\
|
||||
([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users_with_lists]
|
||||
users_with_lists = [
|
||||
list(l[:-4])
|
||||
+ ([[]] if l[-4] == [None] else [list(set(l[-4]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-3]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users
|
||||
]
|
||||
|
||||
users_with_lists = [
|
||||
list(l[:-4])
|
||||
+ ([[]] if l[-4] == [None] else [list(set(l[-4]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-3]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users_with_lists
|
||||
]
|
||||
|
||||
list_dict_users = [dict(zip(headers, r)) for r in users_with_lists]
|
||||
return list_dict_users
|
||||
|
||||
da=DefaultAvatars()
|
||||
|
||||
da = DefaultAvatars()
|
||||
|
|
|
@ -1,77 +1,97 @@
|
|||
#!/usr/bin/env python
|
||||
import time ,os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
from pprint import pprint
|
||||
|
||||
import diceware
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from keycloak import KeycloakAdmin
|
||||
|
||||
from postgres import Postgres
|
||||
|
||||
import diceware
|
||||
options = diceware.handle_options(None)
|
||||
options.wordlist = 'cat_ascii'
|
||||
options.wordlist = "cat_ascii"
|
||||
options.num = 3
|
||||
|
||||
class KeycloakClient():
|
||||
"""https://www.keycloak.org/docs-api/13.0/rest-api/index.html
|
||||
https://github.com/marcospereirampj/python-keycloak
|
||||
https://gist.github.com/kaqfa/99829941121188d7cef8271f93f52f1f
|
||||
"""
|
||||
def __init__(self,
|
||||
url="http://isard-sso-keycloak:8080/auth/",
|
||||
username=os.environ['KEYCLOAK_USER'],
|
||||
password=os.environ['KEYCLOAK_PASSWORD'],
|
||||
realm='master',
|
||||
verify=True):
|
||||
self.url=url
|
||||
self.username=username
|
||||
self.password=password
|
||||
self.realm=realm
|
||||
self.verify=verify
|
||||
|
||||
self.keycloak_pg=Postgres('isard-apps-postgresql','keycloak',os.environ['KEYCLOAK_DB_USER'],os.environ['KEYCLOAK_DB_PASSWORD'])
|
||||
class KeycloakClient:
|
||||
"""https://www.keycloak.org/docs-api/13.0/rest-api/index.html
|
||||
https://github.com/marcospereirampj/python-keycloak
|
||||
https://gist.github.com/kaqfa/99829941121188d7cef8271f93f52f1f
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url="http://isard-sso-keycloak:8080/auth/",
|
||||
username=os.environ["KEYCLOAK_USER"],
|
||||
password=os.environ["KEYCLOAK_PASSWORD"],
|
||||
realm="master",
|
||||
verify=True,
|
||||
):
|
||||
self.url = url
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.realm = realm
|
||||
self.verify = verify
|
||||
|
||||
self.keycloak_pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"keycloak",
|
||||
os.environ["KEYCLOAK_DB_USER"],
|
||||
os.environ["KEYCLOAK_DB_PASSWORD"],
|
||||
)
|
||||
|
||||
def connect(self):
|
||||
self.keycloak_admin = KeycloakAdmin(server_url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm_name=self.realm,
|
||||
verify=self.verify)
|
||||
|
||||
self.keycloak_admin = KeycloakAdmin(
|
||||
server_url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm_name=self.realm,
|
||||
verify=self.verify,
|
||||
)
|
||||
|
||||
def update_pwds(self):
|
||||
self.get_users()
|
||||
|
||||
def get_users(self):
|
||||
self.connect()
|
||||
users=self.get_users_with_groups_and_roles()
|
||||
userupdate=[]
|
||||
users = self.get_users_with_groups_and_roles()
|
||||
userupdate = []
|
||||
for u in users:
|
||||
if u['username'] not in ['admin','ddadmin'] and not u['username'].startswith('system_'):
|
||||
print('Generating password for user '+u['username'])
|
||||
userupdate.append({'id':u['id'],
|
||||
'username':u['username'],
|
||||
'password': diceware.get_passphrase(options=options)})
|
||||
with open("user_temp_passwd.csv","w") as csv:
|
||||
if u["username"] not in ["admin", "ddadmin"] and not u[
|
||||
"username"
|
||||
].startswith("system_"):
|
||||
print("Generating password for user " + u["username"])
|
||||
userupdate.append(
|
||||
{
|
||||
"id": u["id"],
|
||||
"username": u["username"],
|
||||
"password": diceware.get_passphrase(options=options),
|
||||
}
|
||||
)
|
||||
with open("user_temp_passwd.csv", "w") as csv:
|
||||
for user in userupdate:
|
||||
csv.write("%s,%s,%s\n"%(user['id'],user['username'],user['password']))
|
||||
csv.write(
|
||||
"%s,%s,%s\n" % (user["id"], user["username"], user["password"])
|
||||
)
|
||||
|
||||
for u in userupdate:
|
||||
print('Updating keycloak password for user '+u['username'])
|
||||
self.update_user_pwd(u['id'],u['password'])
|
||||
print("Updating keycloak password for user " + u["username"])
|
||||
self.update_user_pwd(u["id"], u["password"])
|
||||
|
||||
def update_user_pwd(self,user_id,password,temporary=True):
|
||||
payload={"credentials":[{"type":"password",
|
||||
"value":password,
|
||||
"temporary":temporary}]}
|
||||
def update_user_pwd(self, user_id, password, temporary=True):
|
||||
payload = {
|
||||
"credentials": [
|
||||
{"type": "password", "value": password, "temporary": temporary}
|
||||
]
|
||||
}
|
||||
self.connect()
|
||||
self.keycloak_admin.update_user( user_id, payload)
|
||||
|
||||
self.keycloak_admin.update_user(user_id, payload)
|
||||
|
||||
def get_users_with_groups_and_roles(self):
|
||||
q = """select u.id, u.username, u.email, u.first_name, u.last_name, u.realm_id, ua.value as quota
|
||||
|
@ -88,7 +108,7 @@ class KeycloakClient():
|
|||
group by u.id,u.username,u.email,u.first_name,u.last_name, u.realm_id, ua.value
|
||||
order by u.username"""
|
||||
|
||||
# q = """select u.id, u.username, u.email, u.first_name, u.last_name, u.realm_id, ua.value as quota, g.id, g.path, g.name,
|
||||
# q = """select u.id, u.username, u.email, u.first_name, u.last_name, u.realm_id, ua.value as quota, g.id, g.path, g.name,
|
||||
# --,json_agg(g."name") as group, json_agg(g_parent."name") as group_parent1, json_agg(g_parent2."name") as group_parent2
|
||||
# --,json_agg(r.name) as role
|
||||
# from user_entity as u
|
||||
|
@ -96,7 +116,7 @@ class KeycloakClient():
|
|||
# left join user_group_membership as ugm on ugm.user_id = u.id
|
||||
# left join keycloak_group as g on g.id = ugm.group_id
|
||||
# --left join keycloak_group as g_parent on g.parent_group = g_parent.id
|
||||
# --left join keycloak_group as g_parent2 on g_parent.parent_group = g_parent2.id
|
||||
# --left join keycloak_group as g_parent2 on g_parent.parent_group = g_parent2.id
|
||||
# left join user_role_mapping as rm on rm.user_id = u.id
|
||||
# left join keycloak_role as r on r.id = rm.role_id
|
||||
# --group by u.id,u.username,u.email,u.first_name,u.last_name, u.realm_id, ua.value
|
||||
|
@ -111,25 +131,34 @@ class KeycloakClient():
|
|||
# left join user_group_membership as ugm on ugm.user_id = u.id
|
||||
# left join keycloak_group as g on g.id = ugm.group_id
|
||||
# left join keycloak_group as g_parent on g.parent_group = g_parent.id
|
||||
# left join keycloak_group as g_parent2 on g_parent.parent_group = g_parent2.id
|
||||
# left join keycloak_group as g_parent2 on g_parent.parent_group = g_parent2.id
|
||||
# left join user_role_mapping as rm on rm.user_id = u.id
|
||||
# left join keycloak_role as r on r.id = rm.role_id
|
||||
# group by u.id,u.username,u.email,u.first_name,u.last_name, u.realm_id, ua.value
|
||||
# order by u.username"""
|
||||
(headers,users)=self.keycloak_pg.select_with_headers(q)
|
||||
(headers, users) = self.keycloak_pg.select_with_headers(q)
|
||||
|
||||
users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-3]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-2]))]) +\
|
||||
([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users]
|
||||
|
||||
users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-3]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-2]))]) +\
|
||||
([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users_with_lists]
|
||||
users_with_lists = [
|
||||
list(l[:-4])
|
||||
+ ([[]] if l[-4] == [None] else [list(set(l[-4]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-3]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users
|
||||
]
|
||||
|
||||
users_with_lists = [
|
||||
list(l[:-4])
|
||||
+ ([[]] if l[-4] == [None] else [list(set(l[-4]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-3]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users_with_lists
|
||||
]
|
||||
|
||||
list_dict_users = [dict(zip(headers, r)) for r in users_with_lists]
|
||||
return list_dict_users
|
||||
|
||||
k=KeycloakClient()
|
||||
|
||||
k = KeycloakClient()
|
||||
k.update_pwds()
|
||||
|
|
|
@ -1,70 +1,88 @@
|
|||
#!/usr/bin/env python
|
||||
import time ,os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
from pprint import pprint
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from keycloak import KeycloakAdmin
|
||||
|
||||
from postgres import Postgres
|
||||
|
||||
|
||||
class KeycloakClient():
|
||||
class KeycloakClient:
|
||||
"""https://www.keycloak.org/docs-api/13.0/rest-api/index.html
|
||||
https://github.com/marcospereirampj/python-keycloak
|
||||
https://gist.github.com/kaqfa/99829941121188d7cef8271f93f52f1f
|
||||
https://github.com/marcospereirampj/python-keycloak
|
||||
https://gist.github.com/kaqfa/99829941121188d7cef8271f93f52f1f
|
||||
"""
|
||||
def __init__(self,
|
||||
url="http://isard-sso-keycloak:8080/auth/",
|
||||
username=os.environ['KEYCLOAK_USER'],
|
||||
password=os.environ['KEYCLOAK_PASSWORD'],
|
||||
realm='master',
|
||||
verify=True):
|
||||
self.url=url
|
||||
self.username=username
|
||||
self.password=password
|
||||
self.realm=realm
|
||||
self.verify=verify
|
||||
|
||||
self.keycloak_pg=Postgres('isard-apps-postgresql','keycloak',os.environ['KEYCLOAK_DB_USER'],os.environ['KEYCLOAK_DB_PASSWORD'])
|
||||
def __init__(
|
||||
self,
|
||||
url="http://isard-sso-keycloak:8080/auth/",
|
||||
username=os.environ["KEYCLOAK_USER"],
|
||||
password=os.environ["KEYCLOAK_PASSWORD"],
|
||||
realm="master",
|
||||
verify=True,
|
||||
):
|
||||
self.url = url
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.realm = realm
|
||||
self.verify = verify
|
||||
|
||||
self.keycloak_pg = Postgres(
|
||||
"isard-apps-postgresql",
|
||||
"keycloak",
|
||||
os.environ["KEYCLOAK_DB_USER"],
|
||||
os.environ["KEYCLOAK_DB_PASSWORD"],
|
||||
)
|
||||
|
||||
def connect(self):
|
||||
self.keycloak_admin = KeycloakAdmin(server_url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm_name=self.realm,
|
||||
verify=self.verify)
|
||||
|
||||
self.keycloak_admin = KeycloakAdmin(
|
||||
server_url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm_name=self.realm,
|
||||
verify=self.verify,
|
||||
)
|
||||
|
||||
def update_pwds(self):
|
||||
self.get_users()
|
||||
|
||||
def get_users(self):
|
||||
self.connect()
|
||||
users=self.get_users_with_groups_and_roles()
|
||||
userupdate=[]
|
||||
users = self.get_users_with_groups_and_roles()
|
||||
userupdate = []
|
||||
for u in users:
|
||||
if u['username'] not in ['admin','ddadmin'] and not u['username'].startswith('system_'):
|
||||
print('Generating password for user '+u['username'])
|
||||
userupdate.append({'id':u['id'],
|
||||
'username':u['username'],
|
||||
'password': diceware.get_passphrase(options=options)})
|
||||
with open("user_temp_passwd.csv","w") as csv:
|
||||
if u["username"] not in ["admin", "ddadmin"] and not u[
|
||||
"username"
|
||||
].startswith("system_"):
|
||||
print("Generating password for user " + u["username"])
|
||||
userupdate.append(
|
||||
{
|
||||
"id": u["id"],
|
||||
"username": u["username"],
|
||||
"password": diceware.get_passphrase(options=options),
|
||||
}
|
||||
)
|
||||
with open("user_temp_passwd.csv", "w") as csv:
|
||||
for user in userupdate:
|
||||
csv.write("%s,%s,%s\n"%(user['id'],user['username'],user['password']))
|
||||
csv.write(
|
||||
"%s,%s,%s\n" % (user["id"], user["username"], user["password"])
|
||||
)
|
||||
|
||||
for u in userupdate:
|
||||
print('Updating keycloak password for user '+u['username'])
|
||||
self.update_user_pwd(u['id'],u['password'])
|
||||
print("Updating keycloak password for user " + u["username"])
|
||||
self.update_user_pwd(u["id"], u["password"])
|
||||
|
||||
def update_user_pwd_temporary(self,temporary=False):
|
||||
payload={"credentials":[{"temporary":temporary}]}
|
||||
def update_user_pwd_temporary(self, temporary=False):
|
||||
payload = {"credentials": [{"temporary": temporary}]}
|
||||
self.connect()
|
||||
self.keycloak_admin.update_user( user_id, payload)
|
||||
self.keycloak_admin.update_user(user_id, payload)
|
||||
|
||||
def get_users_with_groups_and_roles(self):
|
||||
q = """select u.id, u.username, u.email, u.first_name, u.last_name, u.realm_id, ua.value as quota
|
||||
|
@ -81,7 +99,7 @@ class KeycloakClient():
|
|||
group by u.id,u.username,u.email,u.first_name,u.last_name, u.realm_id, ua.value
|
||||
order by u.username"""
|
||||
|
||||
# q = """select u.id, u.username, u.email, u.first_name, u.last_name, u.realm_id, ua.value as quota, g.id, g.path, g.name,
|
||||
# q = """select u.id, u.username, u.email, u.first_name, u.last_name, u.realm_id, ua.value as quota, g.id, g.path, g.name,
|
||||
# --,json_agg(g."name") as group, json_agg(g_parent."name") as group_parent1, json_agg(g_parent2."name") as group_parent2
|
||||
# --,json_agg(r.name) as role
|
||||
# from user_entity as u
|
||||
|
@ -89,7 +107,7 @@ class KeycloakClient():
|
|||
# left join user_group_membership as ugm on ugm.user_id = u.id
|
||||
# left join keycloak_group as g on g.id = ugm.group_id
|
||||
# --left join keycloak_group as g_parent on g.parent_group = g_parent.id
|
||||
# --left join keycloak_group as g_parent2 on g_parent.parent_group = g_parent2.id
|
||||
# --left join keycloak_group as g_parent2 on g_parent.parent_group = g_parent2.id
|
||||
# left join user_role_mapping as rm on rm.user_id = u.id
|
||||
# left join keycloak_role as r on r.id = rm.role_id
|
||||
# --group by u.id,u.username,u.email,u.first_name,u.last_name, u.realm_id, ua.value
|
||||
|
@ -104,25 +122,34 @@ class KeycloakClient():
|
|||
# left join user_group_membership as ugm on ugm.user_id = u.id
|
||||
# left join keycloak_group as g on g.id = ugm.group_id
|
||||
# left join keycloak_group as g_parent on g.parent_group = g_parent.id
|
||||
# left join keycloak_group as g_parent2 on g_parent.parent_group = g_parent2.id
|
||||
# left join keycloak_group as g_parent2 on g_parent.parent_group = g_parent2.id
|
||||
# left join user_role_mapping as rm on rm.user_id = u.id
|
||||
# left join keycloak_role as r on r.id = rm.role_id
|
||||
# group by u.id,u.username,u.email,u.first_name,u.last_name, u.realm_id, ua.value
|
||||
# order by u.username"""
|
||||
(headers,users)=self.keycloak_pg.select_with_headers(q)
|
||||
(headers, users) = self.keycloak_pg.select_with_headers(q)
|
||||
|
||||
users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-3]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-2]))]) +\
|
||||
([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users]
|
||||
|
||||
users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-3]))]) +\
|
||||
([[]] if l[-3] == [None] else [list(set(l[-2]))]) +\
|
||||
([[]] if l[-1] == [None] else [list(set(l[-1]))]) for l in users_with_lists]
|
||||
users_with_lists = [
|
||||
list(l[:-4])
|
||||
+ ([[]] if l[-4] == [None] else [list(set(l[-4]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-3]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users
|
||||
]
|
||||
|
||||
users_with_lists = [
|
||||
list(l[:-4])
|
||||
+ ([[]] if l[-4] == [None] else [list(set(l[-4]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-3]))])
|
||||
+ ([[]] if l[-3] == [None] else [list(set(l[-2]))])
|
||||
+ ([[]] if l[-1] == [None] else [list(set(l[-1]))])
|
||||
for l in users_with_lists
|
||||
]
|
||||
|
||||
list_dict_users = [dict(zip(headers, r)) for r in users_with_lists]
|
||||
return list_dict_users
|
||||
|
||||
k=KeycloakClient()
|
||||
|
||||
k = KeycloakClient()
|
||||
k.update_user_pwd_temporary()
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
#!/usr/bin/env python
|
||||
import time ,os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
from pprint import pprint
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from keycloak import KeycloakAdmin
|
||||
|
||||
from postgres import Postgres
|
||||
|
||||
|
||||
|
|
|
@ -1,28 +1,46 @@
|
|||
#!flask/bin/python
|
||||
# coding=utf-8
|
||||
from gevent import monkey
|
||||
|
||||
monkey.patch_all()
|
||||
|
||||
from flask_socketio import SocketIO, emit, join_room, leave_room, \
|
||||
close_room, rooms, disconnect, send
|
||||
import json
|
||||
|
||||
from flask_socketio import (
|
||||
SocketIO,
|
||||
close_room,
|
||||
disconnect,
|
||||
emit,
|
||||
join_room,
|
||||
leave_room,
|
||||
rooms,
|
||||
send,
|
||||
)
|
||||
|
||||
from admin import app
|
||||
|
||||
app.socketio = SocketIO(app)
|
||||
|
||||
@app.socketio.on('connect', namespace='/sio')
|
||||
|
||||
@app.socketio.on("connect", namespace="/sio")
|
||||
def socketio_connect():
|
||||
join_room('admin')
|
||||
app.socketio.emit('update',
|
||||
json.dumps('Joined'),
|
||||
namespace='/sio',
|
||||
room='admin')
|
||||
|
||||
@app.socketio.on('disconnect', namespace='/sio')
|
||||
join_room("admin")
|
||||
app.socketio.emit("update", json.dumps("Joined"), namespace="/sio", room="admin")
|
||||
|
||||
|
||||
@app.socketio.on("disconnect", namespace="/sio")
|
||||
def socketio_disconnect():
|
||||
None
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.socketio.run(app,host='0.0.0.0', port=9000, debug=False, ssl_context='adhoc', async_mode="threading") #, logger=logger, engineio_logger=engineio_logger)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.socketio.run(
|
||||
app,
|
||||
host="0.0.0.0",
|
||||
port=9000,
|
||||
debug=False,
|
||||
ssl_context="adhoc",
|
||||
async_mode="threading",
|
||||
) # , logger=logger, engineio_logger=engineio_logger)
|
||||
# , cors_allowed_origins="*"
|
||||
# /usr/lib/python3.8/site-packages/certifi
|
||||
# /usr/lib/python3.8/site-packages/certifi
|
||||
|
|
|
@ -1,67 +1,84 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import time, os
|
||||
from datetime import datetime, timedelta
|
||||
import pprint
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import pprint
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import psycopg2
|
||||
import yaml
|
||||
|
||||
from admin.lib.mysql import Mysql
|
||||
from admin.lib.keycloak_client import KeycloakClient
|
||||
from admin.lib.mysql import Mysql
|
||||
|
||||
import string, random
|
||||
app = {}
|
||||
app["config"] = {}
|
||||
|
||||
app={}
|
||||
app['config']={}
|
||||
|
||||
class WordpressSaml():
|
||||
class WordpressSaml:
|
||||
def __init__(self):
|
||||
self.url="http://isard-sso-keycloak:8080/auth/"
|
||||
self.username=os.environ['KEYCLOAK_USER']
|
||||
self.password=os.environ['KEYCLOAK_PASSWORD']
|
||||
self.realm='master'
|
||||
self.verify=True
|
||||
self.url = "http://isard-sso-keycloak:8080/auth/"
|
||||
self.username = os.environ["KEYCLOAK_USER"]
|
||||
self.password = os.environ["KEYCLOAK_PASSWORD"]
|
||||
self.realm = "master"
|
||||
self.verify = True
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
self.db=Mysql('isard-apps-mariadb','wordpress','root',os.environ['MARIADB_PASSWORD'])
|
||||
ready=True
|
||||
self.db = Mysql(
|
||||
"isard-apps-mariadb",
|
||||
"wordpress",
|
||||
"root",
|
||||
os.environ["MARIADB_PASSWORD"],
|
||||
)
|
||||
ready = True
|
||||
except:
|
||||
log.warning('Could not connect to wordpress database. Retrying...')
|
||||
log.warning("Could not connect to wordpress database. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Connected to wordpress database.')
|
||||
log.info("Connected to wordpress database.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./saml_certs/public.cert"),"r") as crt:
|
||||
app['config']['PUBLIC_CERT_RAW']=crt.read()
|
||||
app['config']['PUBLIC_CERT']=self.cert_prepare(app['config']['PUBLIC_CERT_RAW'])
|
||||
ready=True
|
||||
with open(os.path.join("./saml_certs/public.cert"), "r") as crt:
|
||||
app["config"]["PUBLIC_CERT_RAW"] = crt.read()
|
||||
app["config"]["PUBLIC_CERT"] = self.cert_prepare(
|
||||
app["config"]["PUBLIC_CERT_RAW"]
|
||||
)
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get public certificate to be used in wordpress. Retrying...')
|
||||
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
|
||||
log.warning(
|
||||
"Could not get public certificate to be used in wordpress. Retrying..."
|
||||
)
|
||||
log.warning(
|
||||
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
|
||||
)
|
||||
time.sleep(2)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
log.info('Got moodle srt certificate to be used in wordpress.')
|
||||
log.info("Got moodle srt certificate to be used in wordpress.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./saml_certs/private.key"),"r") as pem:
|
||||
app['config']['PRIVATE_KEY']=self.cert_prepare(pem.read())
|
||||
ready=True
|
||||
with open(os.path.join("./saml_certs/private.key"), "r") as pem:
|
||||
app["config"]["PRIVATE_KEY"] = self.cert_prepare(pem.read())
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get private key to be used in wordpress. Retrying...')
|
||||
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
|
||||
log.warning(
|
||||
"Could not get private key to be used in wordpress. Retrying..."
|
||||
)
|
||||
log.warning(
|
||||
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
|
||||
)
|
||||
time.sleep(2)
|
||||
log.info('Got moodle pem certificate to be used in wordpress.')
|
||||
log.info("Got moodle pem certificate to be used in wordpress.")
|
||||
|
||||
# ## This seems related to the fact that the certificate generated the first time does'nt work.
|
||||
# ## And when regenerating the certificate de privatekeypass seems not to be used and instead it
|
||||
|
@ -79,32 +96,34 @@ class WordpressSaml():
|
|||
self.reset_saml()
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
print('Error resetting saml on wordpress')
|
||||
print("Error resetting saml on wordpress")
|
||||
try:
|
||||
self.delete_keycloak_wordpress_saml_plugin()
|
||||
except:
|
||||
print('Error resetting saml on keycloak')
|
||||
|
||||
print("Error resetting saml on keycloak")
|
||||
|
||||
try:
|
||||
self.set_wordpress_saml_plugin()
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
print('Error adding saml on wordpress')
|
||||
|
||||
print("Error adding saml on wordpress")
|
||||
|
||||
try:
|
||||
self.add_keycloak_wordpress_saml()
|
||||
except:
|
||||
print('Error adding saml on keycloak')
|
||||
print("Error adding saml on keycloak")
|
||||
|
||||
# SAML clients don't work well with composite roles so disabling and adding on realm
|
||||
# self.add_client_roles()
|
||||
|
||||
def connect(self):
|
||||
self.keycloak= KeycloakClient(url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm=self.realm,
|
||||
verify=self.verify)
|
||||
self.keycloak = KeycloakClient(
|
||||
url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm=self.realm,
|
||||
verify=self.verify,
|
||||
)
|
||||
|
||||
# def activate_saml_plugin(self):
|
||||
# ## After you need to purge moodle caches: /var/www/html # php admin/cli/purge_caches.php
|
||||
|
@ -113,28 +132,29 @@ class WordpressSaml():
|
|||
# def get_privatekey_pass(self):
|
||||
# return self.db.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2]
|
||||
|
||||
def cert_prepare(self,cert):
|
||||
return ''.join(cert.split('-----')[2].splitlines())
|
||||
def cert_prepare(self, cert):
|
||||
return "".join(cert.split("-----")[2].splitlines())
|
||||
|
||||
def parse_idp_cert(self):
|
||||
self.connect()
|
||||
rsa=self.keycloak.get_server_rsa_key()
|
||||
self.keycloak=None
|
||||
return rsa['certificate']
|
||||
rsa = self.keycloak.get_server_rsa_key()
|
||||
self.keycloak = None
|
||||
return rsa["certificate"]
|
||||
|
||||
def set_keycloak_wordpress_saml_plugin(self):
|
||||
self.connect()
|
||||
self.keycloak.add_wordpress_client()
|
||||
self.keycloak=None
|
||||
self.keycloak = None
|
||||
|
||||
def delete_keycloak_wordpress_saml_plugin(self):
|
||||
self.connect()
|
||||
self.keycloak.delete_client('630601f8-25d1-4822-8741-c93affd2cd84')
|
||||
self.keycloak=None
|
||||
self.keycloak.delete_client("630601f8-25d1-4822-8741-c93affd2cd84")
|
||||
self.keycloak = None
|
||||
|
||||
def set_wordpress_saml_plugin(self):
|
||||
# ('active_plugins', 'a:2:{i:0;s:33:\"edwiser-bridge/edwiser-bridge.php\";i:1;s:17:\"onelogin_saml.php\";}', 'yes'),
|
||||
self.db.update("""INSERT INTO wp_options (option_name, option_value, autoload) VALUES
|
||||
self.db.update(
|
||||
"""INSERT INTO wp_options (option_name, option_value, autoload) VALUES
|
||||
('onelogin_saml_enabled', 'on', 'yes'),
|
||||
('onelogin_saml_idp_entityid', 'Saml Login', 'yes'),
|
||||
('onelogin_saml_idp_sso', 'https://sso.%s/auth/realms/master/protocol/saml', 'yes'),
|
||||
|
@ -194,103 +214,139 @@ class WordpressSaml():
|
|||
('onelogin_saml_advanced_settings_sp_x509cert', '%s', 'yes'),
|
||||
('onelogin_saml_advanced_settings_sp_privatekey', '%s', 'yes'),
|
||||
('onelogin_saml_advanced_signaturealgorithm', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', 'yes'),
|
||||
('onelogin_saml_advanced_digestalgorithm', 'http://www.w3.org/2000/09/xmldsig#sha1', 'yes');""" % (os.environ['DOMAIN'],os.environ['DOMAIN'],self.parse_idp_cert(),app['config']['PUBLIC_CERT'],app['config']['PRIVATE_KEY']))
|
||||
|
||||
('onelogin_saml_advanced_digestalgorithm', 'http://www.w3.org/2000/09/xmldsig#sha1', 'yes');"""
|
||||
% (
|
||||
os.environ["DOMAIN"],
|
||||
os.environ["DOMAIN"],
|
||||
self.parse_idp_cert(),
|
||||
app["config"]["PUBLIC_CERT"],
|
||||
app["config"]["PRIVATE_KEY"],
|
||||
)
|
||||
)
|
||||
|
||||
def reset_saml(self):
|
||||
self.db.update("""DELETE FROM wp_options WHERE option_name LIKE 'onelogin_saml_%'""")
|
||||
self.db.update(
|
||||
"""DELETE FROM wp_options WHERE option_name LIKE 'onelogin_saml_%'"""
|
||||
)
|
||||
|
||||
def add_keycloak_wordpress_saml(self):
|
||||
client={"id" : "630601f8-25d1-4822-8741-c93affd2cd84",
|
||||
"clientId" : "php-saml",
|
||||
"surrogateAuthRequired" : False,
|
||||
"enabled" : True,
|
||||
"alwaysDisplayInConsole" : False,
|
||||
"clientAuthenticatorType" : "client-secret",
|
||||
"redirectUris" : [ "https://wp."+os.environ['DOMAIN']+"/wp-login.php?saml_acs" ],
|
||||
"webOrigins" : [ "https://wp."+os.environ['DOMAIN'] ],
|
||||
"notBefore" : 0,
|
||||
"bearerOnly" : False,
|
||||
"consentRequired" : False,
|
||||
"standardFlowEnabled" : True,
|
||||
"implicitFlowEnabled" : False,
|
||||
"directAccessGrantsEnabled" : False,
|
||||
"serviceAccountsEnabled" : False,
|
||||
"publicClient" : False,
|
||||
"frontchannelLogout" : True,
|
||||
"protocol" : "saml",
|
||||
"attributes" : {
|
||||
"saml.force.post.binding" : True,
|
||||
"saml_assertion_consumer_url_post" : "https://wp."+os.environ['DOMAIN']+"/wp-login.php?saml_acs",
|
||||
"saml.server.signature" : True,
|
||||
"saml.server.signature.keyinfo.ext" : False,
|
||||
"saml.signing.certificate" : app['config']['PUBLIC_CERT_RAW'],
|
||||
"saml_single_logout_service_url_redirect" : "https://wp."+os.environ['DOMAIN']+"/wp-login.php?saml_sls",
|
||||
"saml.signature.algorithm" : "RSA_SHA256",
|
||||
"saml_force_name_id_format" : False,
|
||||
"saml.client.signature" : True,
|
||||
"saml.authnstatement" : True,
|
||||
"saml_name_id_format" : "username",
|
||||
"saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#"
|
||||
client = {
|
||||
"id": "630601f8-25d1-4822-8741-c93affd2cd84",
|
||||
"clientId": "php-saml",
|
||||
"surrogateAuthRequired": False,
|
||||
"enabled": True,
|
||||
"alwaysDisplayInConsole": False,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"redirectUris": [
|
||||
"https://wp." + os.environ["DOMAIN"] + "/wp-login.php?saml_acs"
|
||||
],
|
||||
"webOrigins": ["https://wp." + os.environ["DOMAIN"]],
|
||||
"notBefore": 0,
|
||||
"bearerOnly": False,
|
||||
"consentRequired": False,
|
||||
"standardFlowEnabled": True,
|
||||
"implicitFlowEnabled": False,
|
||||
"directAccessGrantsEnabled": False,
|
||||
"serviceAccountsEnabled": False,
|
||||
"publicClient": False,
|
||||
"frontchannelLogout": True,
|
||||
"protocol": "saml",
|
||||
"attributes": {
|
||||
"saml.force.post.binding": True,
|
||||
"saml_assertion_consumer_url_post": "https://wp."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/wp-login.php?saml_acs",
|
||||
"saml.server.signature": True,
|
||||
"saml.server.signature.keyinfo.ext": False,
|
||||
"saml.signing.certificate": app["config"]["PUBLIC_CERT_RAW"],
|
||||
"saml_single_logout_service_url_redirect": "https://wp."
|
||||
+ os.environ["DOMAIN"]
|
||||
+ "/wp-login.php?saml_sls",
|
||||
"saml.signature.algorithm": "RSA_SHA256",
|
||||
"saml_force_name_id_format": False,
|
||||
"saml.client.signature": True,
|
||||
"saml.authnstatement": True,
|
||||
"saml_name_id_format": "username",
|
||||
"saml_signature_canonicalization_method": "http://www.w3.org/2001/10/xml-exc-c14n#",
|
||||
},
|
||||
"authenticationFlowBindingOverrides": {},
|
||||
"fullScopeAllowed": True,
|
||||
"nodeReRegistrationTimeout": -1,
|
||||
"protocolMappers": [
|
||||
{
|
||||
"id": "72c6175e-bd07-4c27-abd6-4e4ae38d834b",
|
||||
"name": "username",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-attribute-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "Basic",
|
||||
"user.attribute": "username",
|
||||
"friendly.name": "username",
|
||||
"attribute.name": "username",
|
||||
},
|
||||
},
|
||||
"authenticationFlowBindingOverrides" : { },
|
||||
"fullScopeAllowed" : True,
|
||||
"nodeReRegistrationTimeout" : -1,
|
||||
"protocolMappers" : [ {
|
||||
"id" : "72c6175e-bd07-4c27-abd6-4e4ae38d834b",
|
||||
"name" : "username",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-attribute-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "Basic",
|
||||
"user.attribute" : "username",
|
||||
"friendly.name" : "username",
|
||||
"attribute.name" : "username"
|
||||
}
|
||||
}, {
|
||||
"id" : "abd6562f-4732-4da9-987f-b1a6ad6605fa",
|
||||
"name" : "roles",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-role-list-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"single" : True,
|
||||
"attribute.nameformat" : "Basic",
|
||||
"friendly.name" : "Roles",
|
||||
"attribute.name" : "Role"
|
||||
}
|
||||
}, {
|
||||
"id" : "50aafb71-d91c-4bc7-bb60-e1ae0222aab3",
|
||||
"name" : "email",
|
||||
"protocol" : "saml",
|
||||
"protocolMapper" : "saml-user-property-mapper",
|
||||
"consentRequired" : False,
|
||||
"config" : {
|
||||
"attribute.nameformat" : "Basic",
|
||||
"user.attribute" : "email",
|
||||
"friendly.name" : "email",
|
||||
"attribute.name" : "email"
|
||||
}
|
||||
} ],
|
||||
"defaultClientScopes" : [ "web-origins", "role_list", "roles", "profile", "email" ],
|
||||
"optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ],
|
||||
"access" : {
|
||||
"view" : True,
|
||||
"configure" : True,
|
||||
"manage" : True
|
||||
}
|
||||
}
|
||||
{
|
||||
"id": "abd6562f-4732-4da9-987f-b1a6ad6605fa",
|
||||
"name": "roles",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-role-list-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"single": True,
|
||||
"attribute.nameformat": "Basic",
|
||||
"friendly.name": "Roles",
|
||||
"attribute.name": "Role",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "50aafb71-d91c-4bc7-bb60-e1ae0222aab3",
|
||||
"name": "email",
|
||||
"protocol": "saml",
|
||||
"protocolMapper": "saml-user-property-mapper",
|
||||
"consentRequired": False,
|
||||
"config": {
|
||||
"attribute.nameformat": "Basic",
|
||||
"user.attribute": "email",
|
||||
"friendly.name": "email",
|
||||
"attribute.name": "email",
|
||||
},
|
||||
},
|
||||
],
|
||||
"defaultClientScopes": [
|
||||
"web-origins",
|
||||
"role_list",
|
||||
"roles",
|
||||
"profile",
|
||||
"email",
|
||||
],
|
||||
"optionalClientScopes": [
|
||||
"address",
|
||||
"phone",
|
||||
"offline_access",
|
||||
"microprofile-jwt",
|
||||
],
|
||||
"access": {"view": True, "configure": True, "manage": True},
|
||||
}
|
||||
self.connect()
|
||||
self.keycloak.add_client(client)
|
||||
self.keycloak=None
|
||||
self.keycloak = None
|
||||
|
||||
def add_client_roles(self):
|
||||
self.connect()
|
||||
self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','admin','Wordpress admins')
|
||||
self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','manager','Wordpress managers')
|
||||
self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','teacher','Wordpress teachers')
|
||||
self.keycloak.add_client_role('630601f8-25d1-4822-8741-c93affd2cd84','student','Wordpress students')
|
||||
self.keycloak=None
|
||||
self.keycloak.add_client_role(
|
||||
"630601f8-25d1-4822-8741-c93affd2cd84", "admin", "Wordpress admins"
|
||||
)
|
||||
self.keycloak.add_client_role(
|
||||
"630601f8-25d1-4822-8741-c93affd2cd84", "manager", "Wordpress managers"
|
||||
)
|
||||
self.keycloak.add_client_role(
|
||||
"630601f8-25d1-4822-8741-c93affd2cd84", "teacher", "Wordpress teachers"
|
||||
)
|
||||
self.keycloak.add_client_role(
|
||||
"630601f8-25d1-4822-8741-c93affd2cd84", "student", "Wordpress students"
|
||||
)
|
||||
self.keycloak = None
|
||||
|
||||
nw=WordpressSaml()
|
||||
|
||||
nw = WordpressSaml()
|
||||
|
|
|
@ -1,67 +1,84 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
import time, os
|
||||
from datetime import datetime, timedelta
|
||||
import pprint
|
||||
|
||||
import json
|
||||
import logging as log
|
||||
import os
|
||||
import pprint
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import traceback
|
||||
import yaml, json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import psycopg2
|
||||
import yaml
|
||||
|
||||
from admin.lib.mysql import Mysql
|
||||
from admin.lib.keycloak_client import KeycloakClient
|
||||
from admin.lib.mysql import Mysql
|
||||
|
||||
import string, random
|
||||
app = {}
|
||||
app["config"] = {}
|
||||
|
||||
app={}
|
||||
app['config']={}
|
||||
|
||||
class WordpressSaml():
|
||||
class WordpressSaml:
|
||||
def __init__(self):
|
||||
self.url="http://isard-sso-keycloak:8080/auth/"
|
||||
self.username=os.environ['KEYCLOAK_USER']
|
||||
self.password=os.environ['KEYCLOAK_PASSWORD']
|
||||
self.realm='master'
|
||||
self.verify=True
|
||||
self.url = "http://isard-sso-keycloak:8080/auth/"
|
||||
self.username = os.environ["KEYCLOAK_USER"]
|
||||
self.password = os.environ["KEYCLOAK_PASSWORD"]
|
||||
self.realm = "master"
|
||||
self.verify = True
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
self.db=Mysql('isard-apps-mariadb','wordpress','root',os.environ['MARIADB_PASSWORD'])
|
||||
ready=True
|
||||
self.db = Mysql(
|
||||
"isard-apps-mariadb",
|
||||
"wordpress",
|
||||
"root",
|
||||
os.environ["MARIADB_PASSWORD"],
|
||||
)
|
||||
ready = True
|
||||
except:
|
||||
log.warning('Could not connect to wordpress database. Retrying...')
|
||||
log.warning("Could not connect to wordpress database. Retrying...")
|
||||
time.sleep(2)
|
||||
log.info('Connected to wordpress database.')
|
||||
log.info("Connected to wordpress database.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./saml_certs/public.cert"),"r") as crt:
|
||||
app['config']['PUBLIC_CERT_RAW']=crt.read()
|
||||
app['config']['PUBLIC_CERT']=self.cert_prepare(app['config']['PUBLIC_CERT_RAW'])
|
||||
ready=True
|
||||
with open(os.path.join("./saml_certs/public.cert"), "r") as crt:
|
||||
app["config"]["PUBLIC_CERT_RAW"] = crt.read()
|
||||
app["config"]["PUBLIC_CERT"] = self.cert_prepare(
|
||||
app["config"]["PUBLIC_CERT_RAW"]
|
||||
)
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get public certificate to be used in wordpress. Retrying...')
|
||||
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
|
||||
log.warning(
|
||||
"Could not get public certificate to be used in wordpress. Retrying..."
|
||||
)
|
||||
log.warning(
|
||||
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
|
||||
)
|
||||
time.sleep(2)
|
||||
except:
|
||||
log.error(traceback.format_exc())
|
||||
log.info('Got moodle srt certificate to be used in wordpress.')
|
||||
log.info("Got moodle srt certificate to be used in wordpress.")
|
||||
|
||||
ready=False
|
||||
ready = False
|
||||
while not ready:
|
||||
try:
|
||||
with open(os.path.join("./saml_certs/private.key"),"r") as pem:
|
||||
app['config']['PRIVATE_KEY']=self.cert_prepare(pem.read())
|
||||
ready=True
|
||||
with open(os.path.join("./saml_certs/private.key"), "r") as pem:
|
||||
app["config"]["PRIVATE_KEY"] = self.cert_prepare(pem.read())
|
||||
ready = True
|
||||
except IOError:
|
||||
log.warning('Could not get private key to be used in wordpress. Retrying...')
|
||||
log.warning(' You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert')
|
||||
log.warning(
|
||||
"Could not get private key to be used in wordpress. Retrying..."
|
||||
)
|
||||
log.warning(
|
||||
" You should generate them: /admin/saml_certs # openssl req -nodes -new -x509 -keyout private.key -out public.cert"
|
||||
)
|
||||
time.sleep(2)
|
||||
log.info('Got moodle pem certificate to be used in wordpress.')
|
||||
log.info("Got moodle pem certificate to be used in wordpress.")
|
||||
|
||||
# ## This seems related to the fact that the certificate generated the first time does'nt work.
|
||||
# ## And when regenerating the certificate de privatekeypass seems not to be used and instead it
|
||||
|
@ -79,14 +96,14 @@ class WordpressSaml():
|
|||
self.reset_saml_certs()
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
print('Error resetting saml certs on wordpress')
|
||||
print("Error resetting saml certs on wordpress")
|
||||
|
||||
try:
|
||||
self.set_wordpress_saml_plugin_certs()
|
||||
except:
|
||||
print(traceback.format_exc())
|
||||
print('Error adding saml on wordpress')
|
||||
|
||||
print("Error adding saml on wordpress")
|
||||
|
||||
# SAML clients don't work well with composite roles so disabling and adding on realm
|
||||
# self.add_client_roles()
|
||||
|
||||
|
@ -98,32 +115,47 @@ class WordpressSaml():
|
|||
# return self.db.select("""SELECT * FROM "mdl_config" WHERE "name" = 'siteidentifier'""")[0][2]
|
||||
|
||||
def connect(self):
|
||||
self.keycloak= KeycloakClient(url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm=self.realm,
|
||||
verify=self.verify)
|
||||
self.keycloak = KeycloakClient(
|
||||
url=self.url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
realm=self.realm,
|
||||
verify=self.verify,
|
||||
)
|
||||
|
||||
def cert_prepare(self,cert):
|
||||
return ''.join(cert.split('-----')[2].splitlines())
|
||||
def cert_prepare(self, cert):
|
||||
return "".join(cert.split("-----")[2].splitlines())
|
||||
|
||||
def parse_idp_cert(self):
|
||||
self.connect()
|
||||
rsa=self.keycloak.get_server_rsa_key()
|
||||
self.keycloak=None
|
||||
return rsa['certificate']
|
||||
rsa = self.keycloak.get_server_rsa_key()
|
||||
self.keycloak = None
|
||||
return rsa["certificate"]
|
||||
|
||||
def set_wordpress_saml_plugin_certs(self):
|
||||
# ('active_plugins', 'a:2:{i:0;s:33:\"edwiser-bridge/edwiser-bridge.php\";i:1;s:17:\"onelogin_saml.php\";}', 'yes'),
|
||||
self.db.update("""INSERT INTO wp_options (option_name, option_value, autoload) VALUES
|
||||
self.db.update(
|
||||
"""INSERT INTO wp_options (option_name, option_value, autoload) VALUES
|
||||
('onelogin_saml_idp_x509cert', '%s', 'yes'),
|
||||
('onelogin_saml_advanced_settings_sp_x509cert', '%s', 'yes'),
|
||||
('onelogin_saml_advanced_settings_sp_privatekey', '%s', 'yes');""" % (self.parse_idp_cert(),app['config']['PUBLIC_CERT'],app['config']['PRIVATE_KEY']))
|
||||
|
||||
('onelogin_saml_advanced_settings_sp_privatekey', '%s', 'yes');"""
|
||||
% (
|
||||
self.parse_idp_cert(),
|
||||
app["config"]["PUBLIC_CERT"],
|
||||
app["config"]["PRIVATE_KEY"],
|
||||
)
|
||||
)
|
||||
|
||||
def reset_saml_certs(self):
|
||||
self.db.update("""DELETE FROM wp_options WHERE option_name = 'onelogin_saml_idp_x509cert'""")
|
||||
self.db.update("""DELETE FROM wp_options WHERE option_name = 'onelogin_saml_advanced_settings_sp_x509cert'""")
|
||||
self.db.update("""DELETE FROM wp_options WHERE option_name = 'onelogin_saml_advanced_settings_sp_privatekey'""")
|
||||
self.db.update(
|
||||
"""DELETE FROM wp_options WHERE option_name = 'onelogin_saml_idp_x509cert'"""
|
||||
)
|
||||
self.db.update(
|
||||
"""DELETE FROM wp_options WHERE option_name = 'onelogin_saml_advanced_settings_sp_x509cert'"""
|
||||
)
|
||||
self.db.update(
|
||||
"""DELETE FROM wp_options WHERE option_name = 'onelogin_saml_advanced_settings_sp_privatekey'"""
|
||||
)
|
||||
|
||||
nw=WordpressSaml()
|
||||
|
||||
nw = WordpressSaml()
|
||||
|
|
Loading…
Reference in New Issue