parent child groups

root 2021-05-31 15:28:06 +02:00
parent defd0b51fe
commit c02ef731af
17 changed files with 277 additions and 29 deletions

View File

@ -16,4 +16,11 @@ zope.event==4.4
zope.interface==5.1.0
psycopg2==2.8.6
Flask-SocketIO==2.8.6
mysql-connector-python==8.0.25
mysql-connector-python==8.0.25
#Flask-SocketIO==2.8.6
#gevent==1.4.0
#greenlet==0.4.15
python-engineio==3.8.1
python-socketio==4.1.0

View File

@ -32,6 +32,8 @@ Postup()
from admin.lib.admin import Admin
app.admin=Admin()
app.ready=False
'''
Debug should be removed on production!
'''
@ -84,3 +86,4 @@ from .views import AvatarViews

View File

@ -30,6 +30,19 @@ class Admin():
sleep(2)
log.warning('Keycloak connected.')
# try:
# self.keycloak.add_group('parent1')
# except:
# pass
# try:
# self.keycloak.add_group('child1','/parent1')
# except:
# pass
# try:
# self.keycloak.add_group('child2','/parent1')
# except:
# pass
ready=False
while not ready:
try:
@ -306,6 +319,7 @@ class Admin():
theuser={**moodle_exists[0], **theuser}
theuser['moodle']=True
theuser['moodle_groups']=moodle_exists[0]['groups']
theuser['moodle_id']=moodle_exists[0]['id']
else:
theuser['moodle']=False
theuser['moodle_groups']=[]
@ -315,6 +329,7 @@ class Admin():
theuser={**nextcloud_exists[0], **theuser}
theuser['nextcloud']=True
theuser['nextcloud_groups']=nextcloud_exists[0]['groups']
theuser['nextcloud_id']=nextcloud_exists[0]['id']
else:
theuser['nextcloud']=False
theuser['nextcloud_groups']=[]
@ -406,15 +421,18 @@ class Admin():
for g in data['data']['groups']:
# for m in data['data']['d_members']:
groups.append({'provider':data['provider'],
# data['provider']
groups.append({'provider':'external',
"id": g['id'],
"mailid": g['email'].split('@')[0],
"name": g['name'],
"description": g['description']})
self.external['groups']=groups
users=[]
for u in data['data']['users']:
users.append({'provider':data['provider'],
# data['provider']
users.append({'provider':'external',
'id':u['id'],
'email': u['primaryEmail'],
'first': u['name']['givenName'],
@ -424,6 +442,12 @@ class Admin():
'roles':[]})
self.external['users']=users
## Add groups to users (now they only have their orgUnitPath)
for g in self.external['groups']:
for useringroup in data['data']['d_members'][g['mailid']]:
for u in self.external['users']:
if u['id'] == useringroup['id']:
u['groups']=u['groups']+[g['name']]
return True
def sync_external(self):
@ -468,7 +492,41 @@ class Admin():
log.error(traceback.format_exc())
log.warning('Could not remove user: '+u['username'])
def delete_keycloak_groups(self):
def delete_nextcloud_users(self):
for u in self.internal['users']:
if u['nextcloud'] and not u['keycloak']:
if u['roles'] and 'admin' in u['roles']: continue
log.info('Removing nextcloud user: '+u['username'])
try:
self.nextcloud.delete_user(u['nextcloud_id'])
socketio.emit('update',
json.dumps({'status':True,'item':'user','action':'delete','itemdata':u}),
namespace='/isard-sso-admin/sio',
room='admin')
except:
log.error(traceback.format_exc())
log.warning('Could not remove user: '+u['username'])
def delete_moodle_users(self):
userids=[]
usernames=[]
for u in self.internal['users']:
if u['moodle'] and not u['keycloak']:
userids.append(u['moodle_id'])
usernames.append(u['username'])
log.warning('Removing moodle users: '+','.join(usernames))
try:
self.moodle.delete_users(userids)
socketio.emit('update',
json.dumps({'status':True,'item':'user','action':'delete','itemdata':u}),
namespace='/isard-sso-admin/sio',
room='admin')
except:
log.error(traceback.format_exc())
log.warning('Could not remove users: '+','.join(usernames))
def delete_moodle_groups(self):
for g in self.internal['groups']:
if not g['keycloak']: continue
# Do not remove admin group. It should not exist in keycloak, only in nextcloud
@ -478,4 +536,11 @@ class Admin():
self.keycloak.delete_group(g['id'])
except:
log.error(traceback.format_exc())
log.warning('Could not remove group: '+g['name'])
log.warning('Could not remove group: '+g['name'])
def external_roleassign(self,data):
for newuserid in data['ids']:
for externaluser in self.external['users']:
if externaluser['id'] == newuserid:
externaluser['roles']=[data['action']]
return True

View File

@ -164,6 +164,10 @@ class KeycloakClient():
def add_group(self,name,parent=None):
self.connect()
if parent is not None: parent=self.get_group(parent)['id']
print(name)
print(parent)
print('DONE')
return self.keycloak_admin.create_group({"name":name}, parent=parent)
def delete_group(self,group_id):

View File

@ -2,7 +2,7 @@ from requests import get, post
from admin import app
import logging as log
from pprint import pprint
from .postgres import Postgres
@ -72,6 +72,14 @@ class Moodle():
user = self.call('core_user_create_users', users=data)
return user
def delete_user(self, 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)
return user
def get_user_by(self, key, value):
criteria = [{'key': key, 'value': value}]
user = self.call('core_user_get_users', criteria=criteria)

View File

@ -128,7 +128,7 @@ class Nextcloud():
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['group']
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}

View File

@ -54,6 +54,60 @@ $(document).ready(function() {
}
});
$('#action_role').on('change', function () {
action=$(this).val();
names=''
ids=[]
if(users_table.rows('.active').data().length){
$.each(users_table.rows('.active').data(),function(key, value){
names+=value['name']+'\n';
ids.push(value['id']);
});
var text = "You are about to assign role "+action+" these users:\n\n "+names
}else{
$.each(users_table.rows({filter: 'applied'}).data(),function(key, value){
ids.push(value['id']);
});
var text = "You are about to assign role "+action+" "+users_table.rows({filter: 'applied'}).data().length+" users!\n All the users in list!"
}
new PNotify({
title: 'Role assignment!',
text: 'You will asign the role '+action,
hide: false,
opacity: 0.9,
confirm: {
confirm: true
},
buttons: {
closer: false,
sticker: false
},
history: {
history: false
},
addclass: 'pnotify-center'
}).get().on('pnotify.confirm', function() {
$.ajax({
type: "PUT",
url:"/isard-sso-admin/external/roles",
data: JSON.stringify({'ids':ids,'action':action}),
success: function(data)
{
console.log('SUCCESS')
users_table.ajax.reload();
groups_table.ajax.reload();
},
error: function(data)
{
alert('Something went wrong on our side...')
}
});
}).on('pnotify.cancel', function() {
$('#action_role option[value="none"]').prop("selected",true);
});
} );
//DataTable Main renderer
var users_table = $('#users').DataTable({
"ajax": {
@ -74,8 +128,8 @@ $(document).ready(function() {
"width": "10px",
"defaultContent": '<button class="btn btn-xs btn-info" type="button" data-placement="top" ><i class="fa fa-plus"></i></button>'
},
{ "data": "provider", "width": "10px" },
{ "data": "id", "width": "10px" },
// { "data": "provider", "width": "10px" },
// { "data": "id", "width": "10px" },
{ "data": "username", "width": "10px"},
{ "data": "first", "width": "10px"},
{ "data": "last", "width": "10px"},
@ -84,9 +138,15 @@ $(document).ready(function() {
{ "data": "roles", "width": "10px"},
],
"order": [[3, 'asc']],
"columnDefs": [ ]
"columnDefs": [ {
"targets": 5,
"render": function ( data, type, full, meta ) {
return "<li>" + full.groups.join("</li><li>") + "</li>"
}}
]
} );
var groups_table = $('#groups').DataTable({
"ajax": {
"url": "/isard-sso-admin/external_groups_list",
@ -106,8 +166,8 @@ $(document).ready(function() {
"width": "10px",
"defaultContent": '<button class="btn btn-xs btn-info" type="button" data-placement="top" ><i class="fa fa-plus"></i></button>'
},
{ "data": "id", "width": "10px" },
{ "data": "provider", "width": "10px" },
// { "data": "id", "width": "10px" },
// { "data": "provider", "width": "10px" },
{ "data": "name", "width": "10px" },
{ "data": "description", "width": "10px"},
],

View File

@ -1,13 +1,13 @@
// SocketIO
socket = io.connect(location.protocol+'//' + document.domain + ':' + location.port+'/isard-sso-admin/sio');
socket = io.connect(location.protocol+'//' + document.domain +'/isard-sso-admin/sio');
console.log(location.protocol+'//' + document.domain +'/isard-sso-admin/sio')
socket.on('connect', function() {
connection_done();
// connection_done();
console.log('Listening status socket');
});
socket.on('connect_error', function(data) {
connection_lost();
// connection_lost();
});
socket.on('update', function(data) {

View File

@ -112,6 +112,41 @@ $(document).ready(function() {
});
});
$('.btn-delete_nextcloud').on('click', function () {
$.ajax({
type: "DELETE",
url:"/isard-sso-admin/users/nextcloud",
success: function(data)
{
console.log('SUCCESS')
// $("#modalImport").modal('hide');
// users_table.ajax.reload();
// groups_table.ajax.reload();
},
error: function(data)
{
alert('Something went wrong on our side...')
}
});
});
$('.btn-delete_moodle').on('click', function () {
$.ajax({
type: "DELETE",
url:"/isard-sso-admin/users/moodle",
success: function(data)
{
console.log('SUCCESS')
// $("#modalImport").modal('hide');
// users_table.ajax.reload();
// groups_table.ajax.reload();
},
error: function(data)
{
alert('Something went wrong on our side...')
}
});
});
$('.btn-sync_to_moodle').on('click', function () {
$.ajax({
type: "POST",

View File

@ -14,6 +14,19 @@
<div class="x_title">
<h3><i class="fa fa-desktop"></i> External</h3>
<ul class="nav navbar-right panel_toolbox">
<li>
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="action_role">Assign role: <span class="required">*</span></label>
<div class="col-md-6 col-sm-6 col-xs-12">
<select id="action_role" name="action_role" class="form-control action" required>
<option value=''>Select role</option>
<option value='manager'>Manager</option>
<option value='teacher'>Teacher</option>
<option value='student'>Student</option>
</select>
</div>
</div>
</li>
<li>
<a class="btn-sync"><span style="color: #5499c7; "><i class="fa fa-rocket"></i> Sync to system</span></a>
<a class="btn-upload"><span style="color: #5499c7; "><i class="fa fa-upload"></i> Upload</span></a>
@ -28,8 +41,6 @@
<thead>
<tr>
<th></th>
<th>Provider</th>
<th>Id</th>
<th>Username</th>
<th>First</th>
<th>Last</th>
@ -48,8 +59,6 @@
<thead>
<tr>
<th></th>
<th>Id</th>
<th>Provider</th>
<th>Name</th>
<th>Description</th>
</tr>

View File

@ -22,12 +22,12 @@
<input id="id" hidden/>
-->
<div class="item form-group">
<!-- <div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="provider">Provider name: <span class="required">*</span></label>
<div class="col-md-6 col-sm-6 col-xs-12">
<input id="provider" name="provider" placeholder="" type="text" style="width:100%">
</div>
</div>
</div> -->
<div class="item form-group">
<label class="control-label col-md-3 col-sm-3 col-xs-12" for="format">Format: <span class="required">*</span></label>
<div class="col-md-6 col-sm-6 col-xs-12">
@ -46,7 +46,7 @@
</div>
<div class="x_panela" id="bulkusers-quota" style="padding: 5px;">
<!-- <div class="x_panela" id="bulkusers-quota" style="padding: 5px;">
<p style="font-size: 18px;margin-bottom:0px;">Map User keys</p>
<div class="item form-group">
@ -114,7 +114,7 @@
</div>
</div>
</div>
</div>
</div> -->
</div>
<!-- Modal Footer -->

View File

@ -30,6 +30,12 @@
<button class="btn btn-danger btn-xs btn-delete_keycloak">
<i class="fa fa-trash"></i> Delete all keycloak
</button>
<button class="btn btn-danger btn-xs btn-delete_nextcloud">
<i class="fa fa-trash"></i> Delete missing keycloak in nextcloud
</button>
<button class="btn btn-danger btn-xs btn-delete_moodle">
<i class="fa fa-trash"></i> Delete missing keycloak in moodle
</button>
<table id="users" class="table" width="100%">
<thead>
<tr>
@ -68,5 +74,6 @@
<!-- Switchery -->
<script src="/isard-sso-admin/vendors/switchery/dist/switchery.min.js"></script>
<!-- Desktops sse & modals -->
<script src="/isard-sso-admin/static/js/users.js"></script>
<script src="/isard-sso-admin/static/js/users.js"></script>
<script src="/isard-sso-admin/static/js/status_socket.js"></script>
{% endblock %}

View File

@ -23,6 +23,10 @@ def users(provider=False):
if request.method == 'DELETE':
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 provider == 'moodle':
return json.dumps(app.admin.sync_to_moodle()), 200, {'Content-Type': 'application/json'}
@ -79,4 +83,10 @@ def external_users_list():
@app.route('/isard-sso-admin/external_groups_list')
# @login_required
def external_groups_list():
return json.dumps(app.admin.get_external_groups()), 200, {'Content-Type': 'application/json'}
return json.dumps(app.admin.get_external_groups()), 200, {'Content-Type': 'application/json'}
@app.route('/isard-sso-admin/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'}

View File

@ -0,0 +1,16 @@
from flask_socketio import SocketIO, emit, join_room, leave_room, \
close_room, rooms, disconnect, send
from admin import app
socketio = SocketIO(app)
@socketio.on('connect', namespace='/isard-sso-admin/sio')
def socketio_connect():
join_room('admin')
socketio.emit('update',
json.dumps('Joined'),
namespace='/isard-sso-admin/sio',
room='admin')
@socketio.on('disconnect', namespace='/isard-sso-admin/sio')
def socketio_domains_disconnect():
None

View File

@ -134,6 +134,7 @@ class MoodleSaml():
'duallogin': '1',
'idpattr': 'username',
'autocreate': '1',
'anyauth': '1',
'saml_role_siteadmin_map': 'admin',
'saml_role_coursecreator_map': 'teacher',
'saml_role_manager_map': 'manager',

View File

@ -3,8 +3,26 @@
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 admin import app
socketio = SocketIO(app)
socketio.init_app(app, cors_allowed_origins="*")
# from .admin.views.Socketio import *
@socketio.on('connect', namespace='/isard-sso-admin/sio')
def socketio_connect():
join_room('admin')
socketio.emit('update',
json.dumps('Joined'),
namespace='/isard-sso-admin/sio',
room='admin')
@socketio.on('disconnect', namespace='/isard-sso-admin/sio')
def socketio_domains_disconnect():
None
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000, debug=False) #, logger=logger, engineio_logger=engineio_logger)
socketio.run(app,host='0.0.0.0', port=9000, debug=False, cors_allowed_origins="*") #, logger=logger, engineio_logger=engineio_logger)

View File

@ -34,6 +34,9 @@ frontend website
http-request set-header ssl_client_cert -----BEGIN\ CERTIFICATE-----\ %[ssl_c_der,base64]\ -----END\ CERTIFICATE-----\ if { ssl_fc_has_crt }
bind :443 ssl crt /certs/chain.pem
acl is_upgrade hdr(Connection) -i upgrade
acl is_websocket hdr(Upgrade) -i websocket
acl is_nextcloud hdr_beg(host) nextcloud.
acl is_moodle hdr_beg(host) moodle.
acl is_jitsi hdr_beg(host) jitsi.
@ -51,11 +54,13 @@ frontend website
use_backend be_oof if is_oof
use_backend be_wp if is_wp
use_backend be_etherpad if is_pad
use_backend be_admin if is_sso { path_beg /socket.io }
use_backend be_adminer if is_sso { path_beg /isard-sso-adminer }
use_backend be_admin if is_sso { path_beg /isard-sso-admin }
use_backend be_sso if is_sso
use_backend be_ipa if is_ipa
use_backend be_api if is_api
# default_backend be_sso