diff --git a/admin/docker/docker-entrypoint.sh b/admin/docker/docker-entrypoint.sh index b830d5f..daedf29 100755 --- a/admin/docker/docker-entrypoint.sh +++ b/admin/docker/docker-entrypoint.sh @@ -5,5 +5,6 @@ cd /admin/admin yarn install ## End Only in development cd /admin +export PYTHONWARNINGS="ignore:Unverified HTTPS request" python3 start.py & /usr/sbin/sshd -D -e -f /etc/ssh/sshd_config \ No newline at end of file diff --git a/admin/src/admin/lib/admin.py b/admin/src/admin/lib/admin.py index 475822b..4234f65 100644 --- a/admin/src/admin/lib/admin.py +++ b/admin/src/admin/lib/admin.py @@ -39,6 +39,9 @@ class Admin(): sleep(2) log.warning('Keycloak connected.') + # print(self.keycloak.get_user_groups_paths('320b0713-c480-4928-9105-041309d72191')) + # exit(1) + ready=False while not ready: try: @@ -280,14 +283,12 @@ class Admin(): # "roles": []}) # return users_list - # def get_ram_users(self): - # return self.internal['users'] - def get_mix_users(self): return self.internal['users'] def _get_mix_users(self): kusers=self.get_keycloak_users() + pprint(kusers) musers=self.get_moodle_users() nusers=self.get_nextcloud_users() @@ -304,7 +305,7 @@ class Admin(): if len(keycloak_exists): theuser=keycloak_exists[0] theuser['keycloak']=True - theuser['keycloak_groups']=keycloak_exists[0]['groups'] + theuser['keycloak_groups']=self.keycloak.get_user_groups_paths(keycloak_exists[0]['id']) #keycloak_exists[0]['groups'] else: theuser['id']=False theuser['keycloak']=False @@ -377,6 +378,8 @@ class Admin(): if len(keycloak_exists): thegroup=keycloak_exists[0] thegroup['keycloak']=True + thegroup['path']=self.keycloak.get_group_path(keycloak_exists[0]['id']) + del thegroup['subGroups'] else: thegroup['id']=False thegroup['keycloak']=False @@ -402,7 +405,6 @@ class Admin(): thegroup['nextcloud']=False groups.append(thegroup) - return groups def get_external_users(self): @@ -443,7 +445,8 @@ class Admin(): 'username': u['primaryEmail'].split('@')[0], 'groups':[u['orgUnitPath']], ## WARNING: Removing the first 'roles':[], - 'password':diceware.get_passphrase(options=options)}) + 'password':'test'}) + # 'password':diceware.get_passphrase(options=options)}) socketio.emit('update', json.dumps({'status':True,'item':'user','action':'delete','itemdata':''}), namespace='/isard-sso-admin/sio', @@ -460,7 +463,6 @@ class Admin(): return True def sync_external(self,ids): - pprint(ids) log.warning('Starting sync to keycloak') self.sync_to_keycloak() # log.warning('Starting sync to moodle') @@ -495,7 +497,7 @@ class Admin(): if len(u['roles']) != 0: log.warning(' KEYCLOAK USERS: Assign user '+u['username']+' with initial pwd '+ u['password']+' to role '+u['roles'][0]) self.keycloak.assign_realm_roles(uid,u['roles'][0]) - gid=self.keycloak.get_group(path='/'+u['roles'][0])['id'] + gid=self.keycloak.get_group_by_path(path='/'+u['roles'][0])['id'] self.keycloak.group_user_add(uid,gid) # Add user to groups for g in u['groups']: @@ -509,7 +511,7 @@ class Admin(): if sub=='/': continue # User with no path log.warning(' KEYCLOAK USERS: Assign user '+u['username']+' to group '+ str(sub)) - gid=self.keycloak.get_group(path=sub)['id'] + gid=self.keycloak.get_group_by_path(path=sub)['id'] self.keycloak.group_user_add(uid,gid) self.resync_data() @@ -519,7 +521,6 @@ class Admin(): groups=groups+u['keycloak_groups'] groups=list(dict.fromkeys(groups)) - pprint(groups) total=len(groups) i=0 for g in groups: @@ -534,23 +535,91 @@ class Admin(): log.error('probably exists') i=i+1 # print('ADDING FULL PATH: '+str(g)) - return - + + cohorts=self.moodle.get_cohorts() for u in self.internal['users']: if not u['moodle']: log.info('Creating moodle user: '+u['username']) - self.moodle.create_user(u['email'],u['username'],u['password'],u['first'],u['last']) + user=self.moodle.create_user(u['email'],u['username'],'1Random 1String',u['first'],u['last'])[0] + print(str(user)) + user_id=user['id'] + # [{'id': 5, 'username': 'xkrlzwd'}] + + for g in u['keycloak_groups']: + log.info('Adding moodle user: '+u['username']+' to cohort '+g) + cohort_id=[c['id'] for c in cohorts if c['name']==g][0] + + print(user_id) + print(cohort_id) + self.moodle.add_user_to_cohort(user_id,cohort_id) + + ## Update cohorts on existing + self.resync_data() + for u in self.internal['users']: + if u['moodle']: + total=len(groups) + index=0 + for g in groups: + print('THE FULL GROUP TO BE ADDED NOW: '+g) + parts=g.split('/') + subpath='' + for i in range(1,len(parts)): + try: + subpath=subpath+'/'+parts[i] + # log.info('Adding moodle user: '+u['username']+' to cohort '+subpath) + cohort=[c['id'] for c in cohorts if c['name']==subpath][0] + + log.warning(' MOODLE USER GROUPS GROUPS: Adding user '+u['username']+' to group as cohort '+cohort['name']+' ('+str(index)+'/'+str(total)+'): '+subpath) + # print(u['moodle_id']) + # print('Adding to cohort id '+str(cohort_id)+' with name '+g) + self.moodle.add_user_to_cohort(u['moodle_id'],cohort['id']) + except: + log.error('probably exists') + index=index+1 + + ### MISING ASSING USER TO ROLE COHORT + + # for g in u['keycloak_groups']: + # log.info('Adding moodle user: '+u['username']+' to cohort '+g) + # cohort_id=[c['id'] for c in cohorts if c['name']==g][0] + + # print(u['moodle_id']) + # print('Adding to cohort id '+str(cohort_id)+' with name '+g) + # pprint(self.moodle.add_user_to_cohort(u['moodle_id'],cohort_id)) + + def sync_to_nextcloud(self): + groups=[] for u in self.internal['users']: - if not u['nextcloud']: - log.info('Creating nextcloud user: '+u['username']) - group=u['keycloak_groups'][0] if len(u['keycloak_groups']) else False + groups=groups+u['keycloak_groups'] + groups=list(dict.fromkeys(groups)) + + total=len(groups) + i=0 + for g in groups: + parts=g.split('/') + subpath='' + for i in range(1,len(parts)): try: - self.nextcloud.add_user(u['username'],u['password'],2000000000000,group,u['email'],u['first']+' '+u['last']) + log.warning(' NEXTCLOUD GROUPS: Adding group ('+str(i)+'/'+str(total)+'): '+subpath) + subpath=subpath+'/'+parts[i] + self.nextcloud.add_group(subpath) + except: + log.error('probably exists') + i=i+1 + + for u in self.internal['users']: + print('User '+u['username']) + if not u['nextcloud']: + print(' Is not in nextcloud') + log.warning(' NEXTCLOUD USERS: Creating nextcloud user: '+u['username']+' in groups '+str(u['keycloak_groups'])) + # group=u['keycloak_groups'][0] if len(u['keycloak_groups']) else False + try: + self.nextcloud.add_user_with_groups(u['username'],'1Random 1String',500000000000,u['keycloak_groups'],u['email'],u['first']+' '+u['last']) except ProviderItemExists: - log.info('User '+u['username']+' already exists. Skipping...') + log.warning('User '+u['username']+' already exists. Skipping...') continue except: log.error(traceback.format_exc()) @@ -597,17 +666,17 @@ class Admin(): 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)) + if len(userids): + 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_keycloak_groups(self): diff --git a/admin/src/admin/lib/keycloak_client.py b/admin/src/admin/lib/keycloak_client.py index 1847af5..882f2f6 100644 --- a/admin/src/admin/lib/keycloak_client.py +++ b/admin/src/admin/lib/keycloak_client.py @@ -85,34 +85,6 @@ 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, - # --,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 - # left join user_attribute as ua on ua.user_id=u.id and ua.name = 'quota' - # 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 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""" - - # q = """select u.id, u.username, u.email, u.first_name, u.last_name, u.realm_id, ua.value as quota - # ,json_agg(g."name") as group_name,json_agg(g."id") as group_id,json_agg(g."path") as group_path - # ,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 - # left join user_attribute as ua on ua.user_id=u.id and ua.name = 'quota' - # 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 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) users_with_lists = [list(l[:-4])+([[]] if l[-4] == [None] else [list(set(l[-4]))]) +\ @@ -128,6 +100,30 @@ class KeycloakClient(): list_dict_users = [dict(zip(headers, r)) for r in users_with_lists] return list_dict_users + + + def getparent(self,group_id, data): + path = "" + for item in data: + if group_id == item[0]: + path = self.getparent(item[2], data) + path = f'{path}/{item[1]}' + return path + + def get_group_path(self,group_id): + q = """SELECT * FROM keycloak_group""" + groups=self.keycloak_pg.select(q) + return self.getparent(group_id,groups) + + def get_user_groups_paths(self,user_id): + q = """SELECT group_id FROM user_group_membership WHERE user_id = '%s'""" % (user_id) + user_group_ids=self.keycloak_pg.select(q) + + paths=[] + for g in user_group_ids: + paths.append(self.get_group_path(g[0])) + return paths + ## Too slow # def get_users_with_groups_and_roles(self): # self.connect() @@ -213,13 +209,18 @@ class KeycloakClient(): return groups+subgroups+subgroups1 - def get_group(self,path,recursive=True): + 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): self.connect() return self.keycloak_admin.get_group_by_path(path=path,search_in_subgroups=recursive) def add_group(self,name,parent=None,skip_exists=False): self.connect() - if parent is not None: parent=self.get_group(parent)['id'] + print('parent_path: '+str(parent)) + if parent is not 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): @@ -241,14 +242,14 @@ class KeycloakClient(): if parent_path==None: parent_path='/'+parts[i] else: - parent_path=self.get_group(parent_path)['path'] - parent_path=parent_path+parts[i] + parent_path=self.get_group_by_path(parent_path)['path'] + parent_path=parent_path+'/'+parts[i] continue if parent_path==None: parent_path='/'+parts[i] else: - parent_path=parent_path+parts[i] + parent_path=parent_path+'/'+parts[i] # try: # if i == 1: parent_id=self.add_group(parts[i]) @@ -287,7 +288,7 @@ class KeycloakClient(): if thepath=='/': print('Not adding the user '+username+' to any group as does not have any...') continue - gid=self.get_group(path=thepath)['id'] + gid=self.get_group_by_path(path=thepath)['id'] print('Adding '+username+' with uuid: '+uid+' to group '+g+' with uuid: '+gid) self.keycloak_admin.group_user_add(uid,gid) diff --git a/admin/src/admin/lib/moodle.py b/admin/src/admin/lib/moodle.py index 5816ad2..a2cd972 100644 --- a/admin/src/admin/lib/moodle.py +++ b/admin/src/admin/lib/moodle.py @@ -135,12 +135,11 @@ class Moodle(): # user = self.call('core_cohort_add_cohort_members', criteria=criteria) # return user - # def add_users_to_cohort(self,userid,cohortid): - # members=[{'cohorttype':{'type':'system','value':cohortid}, - # 'usertype':{'type':'id','value':userid}}] - # criteria = [{'key': key, 'value': value}] - # 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) + return user def get_cohort_members(self, cohort_id): members = self.call('core_cohort_get_cohort_members', cohortids=[cohort_id])[0]['userids'] diff --git a/admin/src/admin/lib/nextcloud.py b/admin/src/admin/lib/nextcloud.py index 903fb7b..80e76f2 100644 --- a/admin/src/admin/lib/nextcloud.py +++ b/admin/src/admin/lib/nextcloud.py @@ -160,6 +160,41 @@ 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 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 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', + } + 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: + # self.add_group(group) + None + # raise ProviderGroupNotExists + log.error('Get Nextcloud provider user add error: '+str(result)) + raise ProviderOpError + except: + log.error(traceback.format_exc()) + raise + # 100 - successful + # 101 - invalid input data + # 102 - username already exists + # 103 - unknown error occurred whilst adding the user + # 104 - group does not exist + # 105 - insufficient privileges for group + # 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" try: diff --git a/admin/src/admin/static/js/users.js b/admin/src/admin/static/js/users.js index 088d631..1e11a5f 100644 --- a/admin/src/admin/static/js/users.js +++ b/admin/src/admin/static/js/users.js @@ -232,6 +232,11 @@ $(document).ready(function() { }; }}, { + "targets": 7, + "render": function ( data, type, full, meta ) { + return "